几种常用的设计模式(单例、工厂、抽象工厂、适配、桥接、装饰、代理、观察)
设计模式
1.单例模式
定义
保证一个类只有一个实例,并提供一个访问它的全局访问点(方法)。
实现(懒汉式)
public class Singleton { private static Singleton instance; private Singleton () {} public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
优缺点
优点:减少内存开销,避免资源多重占用。
缺点:没有接口,不能继承,与单一职责原则冲突。
使用场景
1> 要求生产唯一序列号。
2> WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
3> 创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。
2.工厂模式
定义
定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。
实现
1> 创建一个接口
public interface Shape { void draw(); }
2> 创建实现接口的实体类
public class Rectangle implements Shape { @Override public void draw() { System.out.println("Inside Rectangle::draw() method."); } } public class Square implements Shape { @Override public void draw() { System.out.println("Inside Square::draw() method."); } }
3> 创建工厂类
public class ShapeFactory { //使用 getShape 方法获取形状类型的对象 public Shape getShape(String shapeType){ if(shapeType == null){ return null; } if(shapeType.equalsIgnoreCase("RECTANGLE")){ return new Rectangle(); } else if(shapeType.equalsIgnoreCase("SQUARE")){ return new Square(); } return null; } }
4> 应用
public class FactoryPatternDemo { public static void main(String[] args) { ShapeFactory shapeFactory = new ShapeFactory(); //获取 Rectangle 的对象,并调用它的 draw 方法 Shape shape2 = shapeFactory.getShape("RECTANGLE"); //调用 Rectangle 的 draw 方法 shape2.draw(); //获取 Square 的对象,并调用它的 draw 方法 Shape shape3 = shapeFactory.getShape("SQUARE"); //调用 Square 的 draw 方法 shape3.draw(); } }
优缺点
优点:
1> 一个调用者想创建一个对象,只要知道其名称就可以了。
2> 扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。
3> 屏蔽产品的具体实现,调用者只关心产品的接口。
缺点:
每增加一个产品,需要一个具体类和对象实现工厂,数量一多,增加系统复杂性,也增加了系统具体类的依赖。
使用场景
1> 日志记录器:记录可能记录到本地硬盘、系统事件、远程服务器等,用户可以选择记录日志到什么地方。
2> 数据库访问,当用户不知道最后系统采用哪一类数据库,以及数据库可能有变化时。
3> 设计一个连接服务器的框架,需要三个协议,"POP3"、"IMAP"、"HTTP",可以把这三个作为产品类,共同实现一个接口。
3.抽象工厂模式
定义
提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
实现
自行百度https://www.runoob.com/design-pattern/abstract-factory-pattern.html
优缺点
优点:
当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。
缺点:
产品族扩展非常困难,要增加一个系列的某一产品,既要在抽象的 Creator 里加代码,又要在具体的里面加代码。
使用场景
1> QQ 换皮肤,一整套一起换。
2> 生成不同操作系统的程序。
4.适配器模式
定义
将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
实现
自行百度https://www.runoob.com/design-pattern/adapter-pattern.html
优缺点
优点:
1> 可以让任何两个没有关联的类一起运行。
2> 提高了类的复用。
3> 增加了类的透明。
4> 灵活性好。
缺点:
1> 过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必 要,可以不使用适配器,而是直接对系统进行重构。
2> 由于 JAVA 至多继承一个类,所以至多只能适配一个适配者类,而且目标类必须是抽象类。
使用场景
有动机地修改一个正常运行的系统的接口,这时应该考虑使用适配器模式。
5.桥接模式
定义
将抽象部分与实现部分分离解耦,使它们都可以独立的变化。
实现
百度https://www.runoob.com/design-pattern/bridge-pattern.html
优缺点
优点:
1> 抽象和实现的分离。
2> 优秀的扩展能力。
3> 实现细节对客户透明。
缺点:
桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。
使用场景
1> 如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系
2> 对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。
3> 一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。
6.装饰模式
定义
动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。
实现
百度https://www.runoob.com/design-pattern/decorator-pattern.html
优缺点
优点:
装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
缺点:
多层装饰比较复杂。
使用场景
1> 扩展一个类的功能。
2> 动态增加功能,动态撤销。
7.代理模式
定义
为其他对象提供一种代理以控制对这个对象的访问。
实现
百度https://www.runoob.com/design-pattern/proxy-pattern.html
优缺点
优点:
1> 职责清晰。
2> 高扩展性。
3> 智能化。
缺点:
1> 由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。
2> 实现代理模式需要额外的工作,有些代理模式的实现非常复杂。
使用场景
动态代理(spring aop)、cglib代理
8.观察者模式
定义
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
实现
百度https://www.runoob.com/design-pattern/observer-pattern.html
java中已经有了对观察者模式的实现
https://blog.csdn.net/hbiao68/article/details/52682858
优缺点
优点:
1> 观察者和被观察者是抽象耦合的。
2> 建立一套触发机制。
缺点:
1> 如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
2> 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
3> 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
使用场景
1> 一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
2> 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
3> 一个对象必须通知其他对象,而并不知道这些对象是谁。
4> 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。