一、设计模式的概念
设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。
二、设计模式的七大原则
1、单一职责原则 ( SRP )
英文全称是Single Responsibility Principle,定义是一个类,应该只有一个引起它变化的原因。类变化的原因就是职责,如果一个类承担的职责过多,就等于把这些职责耦合在一起了。一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力。这种耦合会导致脆弱的设计,当发生变化时,设计会遭受到意想不到的破坏。而如果想要避免这种现象的发生,就要尽可能的遵守单一职责原则。此原则的核心就是解耦和增强内聚性。
2、开闭原则 ( OCP )
英文全称是Open Close Principle,定义是软件实体(包括类、模块
、函数等)应该对于扩展时开放的,对于修改是封闭的。开闭原则是是面向对象设计中最重要的原则之一,其它很多的设计原则都是实现开闭原则的一种手段。
3、里氏替换原则 ( LSP )
英文全称是Liskov Substitution Principle,是面向对象设计的基本原则之一。 定义是任何基类可以出现的地方,子类一定可以出现。LSP 是继承复用的基石,只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。里氏替换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏替换原则是对实现抽象化的具体步骤的规范。
4、依赖倒置原则 ( DIP )
英文全称是Dependence Inversion Principle,这个原则是开闭原则的基础,依赖倒置原则就是要求调用者和被调用者都依赖抽象,这样两者没有直接的关联和接触,在变动的时候,一方的变动不会影响另一方的变动。依赖倒置强调了抽象的重要性,针对接口编程,依赖于抽象而不依赖于具体。
5、接口隔离原则 ( ISP )
英文全称是Interface Segregation Principle,这个原则的意思是使用多个隔离的接口,比使用单个接口要好。目的就是降低类之间的耦合度,便于软件升级和维护。
6、最少知道原则(迪米特原则)
一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。通俗地说就是不要和陌生人说话,即一个对象应对其他对象有尽可能少的了解。迪米特法则的初衷在于降低类之间的耦合。由于每个类尽量减少对其他类的依赖,因此,很容易使得系统的功能模块功能独立,相互之间不存在(或很少有)依赖关系。
7、合成/聚合复用(CARP)
英文全称是Composite Reuse Principle,合成/聚合复用原则经常又叫做合成复用原则。合成/聚合复用原则的潜台词是:我只是用你的方法,我们不一定是同类。继承的耦合性更大,比如一个父类后来添加实现一个接口或者去掉一个接口,那子类可能会遭到毁灭性的编译错误,但如果只是组合聚合,只是引用类的方法,就不会有这种巨大的风险,同时也实现了复用。
三、创建型模式
1.单例模式
确保某一个类只有一个实例,并自行实例化向整个系统提供这个实例。
单例模式理解起来不难,典型例子有一个公司只能有一个CEO。它主要是为了保证一个类仅有一个实例,这个类中自己提供一个返回实例的方法,方法中先判断系统是否已经有这个单例,如果有则返回,如果没有则创建。如果创建多个实例会消耗过多的资源或者某种类型的对象只应该有且只有一个时,应该考虑使用单例模式。
写法一、懒汉式写法
public class Singleton {
private static Singleton instance;
//构造函数私有
private Singleton (){
}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
写法二、饿汉式
public class Singleton {
private final static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
写法三、DCL(Double Check Lock) 双重校验锁
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){
}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
写法四、静态内部类单例模式
public class Singleton {
private Singleton (){
}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
}
写法五、枚举
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}
优点:
- 1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
- 2、避免对资源的多重占用(比如写文件操作)。
缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
使用场景:
- 1、要求生产唯一序列号。
- 2、WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
- 3、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。
2.工厂方法模式
定义一个用于创建对象的接口,让子类决定实例化哪一个类。
工厂方法模式的典型例子,自行车分为山地自行车和公路自行车等,当需要买自行车时,我们直接去自行车厂里告诉厂长我们需要的自行车即可。
1、定义一个接口自行车,Bike。
public interface Bike{
void ride();
}
2、定义实现类山地自行车,MBike
public class MBike implements Bike{
@Override
public void ride() {
System.out.println("MBike Rides... ");
}
}
3、定义实现类公路自行车,RBike
public class RBike implements Bike{
@Override
public void ride() {
System.out.println("RBike Rides... ");
}
}
4、工厂类,负责创建对象
public class BikeFactory {
//使用 getBikr 方法获取形状类型的对象
public Bike getBike(String bikeType){
if(bikeType == null){
return null;
}
if(bikeType.equalsIgnoreCase("MBike")){
return new MBike();
}else if(shapeType.equalsIgnoreCase("RBike")){
return new RBike();
}
return null;
}
}
5、子类决定实例化哪一个类
public class FactoryPatternDemo {
public static void main(String[] args) {
BikeFactory bikeFactory = new BikeFactory();
Bike bike1 = bikeFactory.getBike("MBike");
bike1.ride();
Bike bike2= bikeFactory.getBike("RBike");
bike2.ride();
}
}
优点: 1、一个调用者想创建一个对象,只要知道其名称就可以了。 2、扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。 3、屏蔽产品的具体实现,调用者只关心产品的接口。
缺点:每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事。
3.抽象工厂模式
提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
抽象工厂模式主要解决接口选择的问题,典型例子是手机和电脑问题,假设抽象产品1是手机,具体产品是Android手机和iOS手机,抽象产品2是电脑,具体产品是Windows电脑和Mac电脑,抽象工厂是同时生产手机和电脑的公司,具体工厂是联想公司和苹果公司。
抽象工厂的优点是当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。缺点是产品族扩展非常困难,要增加一个系列的某一产品,既要在抽象工厂里加代码,又要在具体工厂里加代码。
4.建造者模式
将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。
建造者模式主要解决在软件系统中一个复杂对象的创建工作。通常一个复杂对象是由各个部分的子对象用一定的算法构成,由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定。
建造者模式的一个典型例子是Android中的AlertDialog的构建过程。还有个例子是计算机的组装,计算机是个复杂的对象,它是有很多零件组装而成,显示器、操作系统,鼠标等,通过创建Builder接口来控制零件的组装过程,这样当组件发生变化时,虽然经过同样的构建过程,但是最后得到的结果不同。
5.原型模式
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
原型模式不难理解,它主要是用在实例创建的时候,因为有的时候我们通过new创建一个对象时可能成本过高,这时我们可以考虑直接通过直接克隆实例快速创建对象。克隆后的实例与原实例内部属性一致。原型模式需要注意一个深拷贝和浅拷贝的问题。
四、结构型模式
1.适配器模式
将一个类的接口转换成另外一个客户希望的接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
适配器模式包括两种类型,类适配器模式和对象适配器模式。这里主要介绍对象适配器模式,因为它更灵活。适配器模式中主要有三个部分,Target、Adapter和Adaptee,其中Target是目标角色,Adaptee是需要被转换的对象,Adapter就是适配器。例如,现在有个手机充电电压是5v,而插座电压是220v,这时我们可以把充电器看成是Adapter,它将电压进行转化最后得到结果。
// Target 目标接口
public interface Volt5 {
public int getVolt5();
}
// Adaptee 需要被转换的对象
public class Volt220 {
public int getVolt220() {
return 220;
}
}
//Adapter 适配器
public class VoltAdapter implements Volt5 {
Volt220 mVolt220;
public VoltAdapter(Volt220 adaptee) {
mVolt220 = adaptee;
}
public int getVolt220() {
return 220;
}
@Override
public int getVolt5() {
return 5;
}
}
2.装饰模式
动态地给一个对象添加一些额外的职责。就增加功能来说,Decorator模式相比生成子类更为灵活。
装饰模式是作为现有的类的一个包装,它允许向一个现有的对象添加新的功能,同时又不改变其结构。这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。装饰模式主要是装饰器类Decorator的设计,它对指定对象进行装饰。