GOF设计模式(概念、原则、场景、优点、缺点、应⽤)设计模式是软件⼤师们根据多年来的软件开发经验,对软件开发领域包括合理复⽤、提⾼健壮性、减少BUG等各⽅⾯作的抽象总结,不同的设计模式⽅法适合于不同的应⽤场景,是汇结了他们最宝贵的经验总结。最早的开发模式是1994年GOF四⼈共同完成的《Design Patterns - Elements of Reusable Object-Oriented Software》⼀书提及的23种经典设计模式,⾄今仍是设计模式⽅⾯,经典中的经典著作。
⼀、设计模式的六⼤原则:
总原则-开闭原则
对扩展开放,对修改封闭。在程序需要进⾏拓展的时候,不能去修改原有的代码,⽽是要扩展原有代码,实现⼀个热插拔的效果。所以⼀句话概括就是:为了使程序的扩展性好,易于维护和升级。
想要达到这样的效果,我们需要使⽤接⼝和抽象类等,后⾯的具体设计中我们会提到这点。
1、单⼀职责原则
不要存在多于⼀个导致类变更的原因,也就是说每个类应该实现单⼀的职责,否则就应该把类拆分。
2、⾥⽒替换原则(Liskov Substitution Principle)
任何基类可以出现的地⽅,⼦类⼀定可以出现。⾥⽒替换原则是继承复⽤的基⽯,只有当衍⽣类可以替换基类,软件单位的功能不受到影响时,基类才能真正被复⽤,⽽衍⽣类也能够在基类的基础上增加新的⾏为。
⾥⽒代换原则是对“开-闭”原则的补充。实现“开闭”原则的关键步骤就是抽象化。⽽基类与⼦类的继承关系就是抽象化的具体实现,所以⾥⽒代换原则是对实现抽象化的具体步骤的规范。⾥⽒替换原则中,⼦类对⽗类的⽅法尽量不要重写和重载。因为⽗类代表了定义好的结构,通过这个规范的接⼝与外界交互,⼦类不应该随便破坏它。
3、依赖倒转原则(Dependence Inversion Principle)
⾯向接⼝编程,依赖于抽象⽽不依赖于具体。写代码时⽤到具体类时,不与具体类交互,⽽与具体类的上层接⼝交互。
4、接⼝隔离原则(Interface Segregation Principle)
每个接⼝中不存在⼦类⽤不到却必须实现的⽅法,如果不然,就要将接⼝拆分。使⽤多个隔离的接⼝,⽐使⽤单个接⼝(多个接⼝⽅法集合到⼀个的接⼝)要好。
5、迪⽶特法则(最少知道原则)(Demeter Principle)
⼀个类对⾃⼰依赖的类知道的越少越好。⽆论被依赖的类多么复杂,都应该将逻辑封装在⽅法的内部,通过public⽅法提供给外部。这样当被依赖的类变化时,才能最⼩的影响该类。
最少知道原则的另⼀个表达⽅式是:只与直接的朋友通信。类之间只要有耦合关系,就叫朋友关系。耦合分为依赖、关联、聚合、组合等。我们称出现为成员变量、⽅法参数、⽅法返回值中的类为直接朋友。局部变量、临时变量则不是直接的朋友。我们要求陌⽣的类不要作为局部变量出现在类中。
6、合成复⽤原则(Composite Reuse Principle)
尽量⾸先使⽤合成/聚合的⽅式,⽽不是使⽤继承,即组合优于继承.
按照类型,可分为3类:
1、创建型模式:抽象⼯⼚、建造者模式、⼯⼚⽅法、原型模式、单例模式;
创建型模式抽象了实例化的过程。创建性模式隐藏了这些类的实例是如何被创建和放在⼀起,整个系统关于这些对象所知道的是由抽象类所定义的接⼝。这样,创建性模式在创建了什么、谁创建它、她是怎么被创建的、以及何时创建⽅⾯提供了灵活性。创建相应数⽬的原型并克隆她们通常⽐每次⽤适合的状态⼿⼯实例化该类更⽅便。
2、结构型模式:适配器模式、桥接模式、组合模式、装饰者模式、外观模式、享元模式、代理模式;
3、⾏为型模式:观察者模式、模板⽅法、命令模式、状态模式、职责链模式、解释器模式、中介者模式、访问者模式、策略模式、备忘录模式、迭代器模式。
4、MVC模式:集观察者、组合、策略为⼀体,是多种模式的综合应⽤,算是⼀种架构模式。
下⾯按照【概念】+【原则】+【场景】+【优点】+【缺点】+【应⽤】分别简述⼀下24种设计模式:
1、抽象⼯⼚模式(Abstract Factory)
提供⼀个创建⼀系列相关或互相依赖对象的接⼝,⽽⽆需指定它们具体的类。
原则:
LSP ⾥⽒替换原则
场景:
创建不同的产品对象,客户端应使⽤不同的具体⼯⼚。
优点:
1、改变具体⼯⼚即可使⽤不同的产品配置,使改变⼀个应⽤的具体⼯⼚变得很容易。
2、让具体的创建实例过程与客户端分离,客户端通过抽象接⼝操作实例,产品的具体类名也被具体⼯⼚的实现分离。
缺点:
如果要新增⽅法,改动极⼤。
应⽤:
1、jdk中连接数据库的代码是典型的抽象⼯⼚模式,每⼀种数据库只需提供⼀个统⼀的接⼝:Driver(⼯⼚类),并实现其中的⽅法即可。不管是jdbc还是odbc都能够通过扩展产品线来达到连接⾃⾝数据库的⽅法。
2、java.util.Collection 接⼝中定义了⼀个抽象的 iterator() ⽅法,该⽅法就是⼀个⼯⼚⽅法。对于 iterator() ⽅法来说 Collection 就是⼀个抽象⼯⼚。
2、建造者模式(Builder)【⼜名,⽣成器模式】
将⼀个复杂对象的构建与它的表⽰分离,使得同样的构建过程可以创建不同的表⽰。
原则:
依赖倒转原则
场景:
如果需要将⼀个复杂对象的构建与它的表⽰分离,使得同样的构建过程可以创建不同的表⽰。建造者模式是当创建复杂对象的算法应该独⽴于该对象的组成部分以及它们的装配⽅式时适⽤的模式。
优点:
使得建造代码与表⽰代码分离。
缺点:
1、增加代码量;
2、Builder只是⼀个替代构造器的选择,不能直接⽤于降低⾮构造函数⽅法的参数数量。
应⽤:
StringBuilder和StringBuffer的append()⽅法
3、⼯⼚⽅法模式(Factory Method)
定义⼀个⽤于创建对象的接⼝,让⼦类决定实例化哪⼀个类,⼯⼚⽅法使⼀个类的实例化延迟到其⼦类。
原则:
开放封闭原则
场景:
不改变⼯⼚和产品体系,只是要扩展产品(变化)。
优点:
是简单⼯⼚模式的进⼀步抽象和推⼴,既保持了简单⼯⼚模式的优点(⼯⼚类中包含了必要的逻辑判断,根据客户端的选择条件动态实例化相关的类。对于客户端来说,去除了与具体产品的依赖),⽽且克服了简单⼯⼚的缺点(违背了开放封闭原则)。
缺点:
每增加⼀个产品,就需要增加⼀个产品⼯⼚的类,增加了额外的开发。(⽤反射可以解决)。
应⽤:
1、Collection中的iterator⽅法;
2、java.lang.Proxy#newProxyInstance()
3、java.lang.Object#toString()
4、java.lang.Class#newInstance()
5、flect.Array#newInstance()
6、flect.Constructor#newInstance()
7、java.lang.Boolean#valueOf(String)
8、java.lang.Class#forName()
4、原型模式(prototype):【⼜名,⽣成器模式】
⽤原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
原则:
场景:
在初始化信息不发⽣变化的情况,⽤克隆进⾏拷贝。
优点:
隐藏了对象创建的细节,⼤⼤提升了性能。不⽤重新初始化对象,⽽是动态的获得对象运⾏时的状态。
缺点:
深复制 or 浅复制 。
应⽤:
JDK中的Date类。
5、单例模式(Singleton)
保证⼀个类仅有⼀个实例,并提供⼀个访问它的全局访问点。
原则:
封装
场景:
通常,我们可以让⼀个全局变量使得⼀个对象被访问,但它不能防⽌你实例化多个对象,⼀个最好的办法就是,让类⾃⾝负责保存它的唯⼀实例。这个类可以保证没有其他实例可以被创建,⽽且它可以提供⼀个访问该实例的⽅法。
优点:
对唯⼀实例的受控访问。
缺点:
饿汉式/懒汉式 多线程同时访问时可能造成多个实例。
应⽤:
1、java.lang.Runtime; GUI中也有⼀些(java.awt.Toolkit#getDefaultToolkit() java.awt.Desktop#getD
esktop())
6、适配器模式(Adapter)
将⼀个类的接⼝转换成客户希望的另外⼀个接⼝。Adapter模式使得原本由于接⼝不兼容⽽不能⼀起⼯作的那些类可以⼀起⼯作。在GoF的设计模式中,适配器有两种类型,类适配器模式和对象适配器模式。
1、类适配器模式:通过多重继承对⼀个接⼝与另⼀个接⼝进⾏匹配,⽽C#,Java等语⾔都不⽀持多重继承,也就是⼀个类只有⼀个⽗类。
2、Java⼀般都指的是 对象适配器模式
场景:
适配器是为了复⽤⼀些现有的类。系统的数据和⾏为都正确,但是接⼝不符,这时采⽤适配器模式,使原有对象和新接⼝匹配。
优点:
能够复⽤现存的类,客户端统⼀调⽤同⼀接⼝,更简单、直接、紧凑。
缺点:
适配器模式有点⼉“亡⽺补牢”的感觉,设计阶段要避免使⽤。
应⽤:
在Java jdk中,适配器模式使⽤场景很多,如
集合包中Java.util.Arrays#asList()、
IO包中java.io.InputStreamReader(InputStream)、java.io.OutputStreamWriter(OutputStream) 等
7、桥接模式(Bridge)
将抽象部分与它的实现部分分离,使它们都可以独⽴的变化。
原则:
合成/聚合复⽤原则
场景:
实现系统可能有多⾓度分类,每⼀种分类都有可能变化,那么就把这种多⾓度分离出来让它们独⽴变化,减少它们之间的耦合。
优点:
减少各部分的耦合。 分离抽象和实现部分,更好的扩展性,可动态地切换实现、可减少⼦类的个数。
缺点:
1、桥接模式的引⼊会增加系统的理解与设计难度,由于聚合关联关系建⽴在抽象层,要求开发者针对抽象进⾏设计与编程。
2、桥接模式要求正确识别出系统中两个独⽴变化的维度,因此其使⽤范围具有⼀定的局限性
应⽤:
Collections类中的sort()⽅法;AWT;JDBC数据库访问接⼝API;
8、组合模式(Composite)
将对象组合成树形结构以表⽰“部分-整体”的层次结构。
场景:
需求中体现部分与整体层次结构时,以及希望⽤户可以忽略组合对象与单个对象的不同,统⼀使⽤组合结构中的所有对象时,就应该考虑使⽤组合模式了。
优点:
组合模式让客户可以⼀致的使⽤组合结构和单个对象。
缺点:
使设计变得更加抽象,对象的业务规则如果很复杂,则实现组合模式具有很⼤挑战性,⽽且不是所有的⽅法都与叶⼦对象⼦类都有关联。应⽤:
JDK中AWT包和Swing包的设计是基于组合模式,在这些界⾯包中为⽤户提供了⼤量的容器构件(如Container)和成员构件(如Checkbox、Button和TextComponent等),他们都是继承、关联⾃抽象组件类Component。
9、装饰模式(Decorator)
动态地给⼀个对象添加⼀些额外的职责,就增加功能来说,装饰模式⽐⽣成⼦类更灵活。
场景:
装饰模式是为了已有功能动态地添加更多功能的⼀种⽅式,当系统需要新功能的时候,是向旧类中添加新的代码,这些新的代码通常装饰了原有类的核⼼职责或主要⾏为。装饰着模式把每个要装饰的功能放在单独的类中,并让这个类包装它所要装饰的对象,当需要执⾏特殊⾏为时,客户代码就可以在运⾏时根据需要有选择的、按顺序地使⽤装饰功能包装对象。
优点:
把类中的装饰功能从类中搬移出去,简化原有的类。有效的把类的核⼼职责和装饰功能区分开,去除相关类中重复的装饰逻辑。
缺点:
利⽤装饰器模式,常常造成设计中有⼤量的⼩类,数量实在太多,可能会造成使⽤此API程序员的困扰。
应⽤:
Java I/O使⽤装饰模式设计,JDK中还有很多类是使⽤装饰模式设计的,如:Reader类、Writer类、OutputStream类等。
10、外观模式(facade)
为⼦系统中的⼀组接⼝提供⼀个⼀致的界⾯,此模式定义了⼀个⾼层接⼝,这个接⼝使得这⼀⼦系统更加容易使⽤。
原则:
完美的体现了依赖倒转原则和迪⽶特法则。
场景:
1、设计阶段:需有意识的将不同的两个层分离。
2、开发阶段:增加外观façade提供⼀个简单的接⼝,应对⼦类的重演和演化。
3、维护期间:使⽤façade类,为遗留代码提供清晰简单的接⼝,让新系统与façade交互,façade与遗留代码交互所有复杂的⼯作。
优点:
1、客户对⼦系统的使⽤变得简单了,减少了与⼦系统的关联对象,实现了⼦系统与客户之间的松耦合
关系。抽象类的使用
2、只是提供了⼀个访问⼦系统的统⼀⼊⼝,并不影响⽤户直接使⽤⼦系统类
3、降低了⼤型软件系统中的编译依赖性,并简化了系统在不同平台之间的移植过程。
缺点:
1、不能很好地限制客户使⽤⼦系统类,如果对客户访问⼦系统类做太多的限制则减少了可变性和灵活性
2、在不引⼊抽象外观类的情况下,增加新的⼦系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。
11、享元模式(Flyweight)
运⽤共享技术有效的⽀持⼤量细粒度的对象。
场景:
如果⼀个应⽤程序使⽤了⼤量的对象,⽽⼤量的这些对象造成了很⼤存储开销时就应该考虑使⽤享元
模式;还有就是对象⼤多数状态都可为外部状态,如果删除对象的外部状态,那么可以⽤相对较少的共享对象取代很多组对象,此时可以考虑使⽤享元模式。
优点:
享元模式可以避免⼤量⾮常相似类的开销。程序中,⼤量细粒度的类实例来表⽰数据,如果它们除了⼏个参数外基本相同,那么把它们转移到类实例的外⾯,在⽅法调⽤时将它们传递进来,就可以通过共享⼤幅度减少单个实例的数⽬。
缺点:
1、由于享元模式需要区分外部状态和内部状态,使得应⽤程序在某种程度上来说更加复杂化了。
2、为了使对象可以共享,享元模式需要将享元对象的状态外部化,⽽读取外部状态使得运⾏时间变长。
应⽤:
String 类。
12、代理模式(proxy)
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论