设计模式总结(创建型、结构型)
前言
这篇博客主要介绍23种设计模式的适用范围以及他们的优缺点,类图尽量使用了实例的类图来替代,没有找到的类图就用了设计模式本身的结构图。
创建型模式
抽象工厂模式
提供一个创建产品的接口来负责创建相关或依赖的对象,而不具体明确指定具体类
优点:
抽象工厂模式将具体产品的创建延迟到具体工厂的子类中,这样将对象的创建封装起来,可以减少客户端与具体产品类之间的依赖,从而使系统耦合度低,这样更有利于后期的维护和扩展。
缺点:
抽象工厂模式很难支持新种类产品的变化。这是因为抽象工厂接口中已经确定了可以被创建的产品集合,如果需要添加新产品,此时就必须去修改抽象工厂的接口,这样就涉及到抽象工厂类的以及所有子类的改变,这样也就违背了“开发——封闭”原则。
适用场景:
①一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节。
②系统中有多于一个的产品族,而每次只使用其中某一产品族。
③属于同一个产品族的产品将在一起使用,这一约束必须在系统的设计中体现出来。
④产品等级结构稳定,设计完成之后,不会向系统中增加新的产品等级结构或者删除已有的产品等级结构。
建造者模式
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。建造者模式的本质是使组装过程(用指挥者类进行封装,从而达到解耦的目的)和创建具体产品解耦,使我们不用去关心每个组件是如何组装的。
建造者模式的实现:
①在建造者模式中,指挥者是直接与客户端打交道的,指挥者将客户端创建产品的请求划分为对各个部件的建造请求,再将这些请求委派到具体建造者角色,具体建造者角色是完成具体产品的构建工作的,却不为客户所知道。
②建造者模式主要用于“分步骤来构建一个复杂的对象”,其中“分步骤”是一个固定的组合过程,而复杂对象的各个部分是经常变化的(也就是说电脑的内部组件是经常变化的,这里指的的变化如硬盘的大小变了,CPU由单核变双核等)。
③产品不需要抽象类,由于建造模式的创建出来的最终产品可能差异很大,所以不大可能提炼出一个抽象产品类。
④在前面文章中介绍的抽象工厂模式解决了“系列产品”的需求变化,而建造者模式解决的是 “产品部分” 的需要变化。
⑤由于建造者隐藏了具体产品的组装过程,所以要改变一个产品的内部表示,只需要再实现一个具体的建造者就可以了,从而能很好地应对产品组成组件的需求变化。
工厂方法模式
定义一个用于创建对象的接口,让子类决定将哪一个类实例化。工厂方法模式让一个类的实例化延迟到其子类。工厂方法模式又简称为工厂模式(Factory Pattern),又可称作虚拟构造器模式(VirtualConstructor Pattern)或多态工厂模式(Polymorphic FactoryPattern)。
工厂方法模式之所以可以解决简单工厂的模式,是因为它的实现把具体产品的创建推迟到子类中,此时工厂类不再负责所有产品的创建,而只是给出具体工厂必须实现的接口,这样工厂方法模式就可以允许系统不修改工厂类逻辑的情况下来添加新产品,这样也就克服了简单工厂模式中缺点。如果系统需要添加新产品时,我们可以利用多态性来完成系统的扩展,对于抽象工厂类和具体工厂中的代码都不需要做任何改动。
原型模式
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
优点:
①原型模式向客户隐藏了创建新实例的复杂性
②原型模式允许动态增加或较少产品类。
③原型模式简化了实例的创建结构,工厂方法模式需要有一个与产品类等级结构相同的等级结构,而原型模式不需要这样。
④产品类不需要事先确定产品的等级结构,因为原型模式适用于任何的等级结构
缺点:
①每个类必须配备一个克隆方法
②配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。
单例模式
确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。
优点:
①单例模式具有一定的伸缩性,类自己来控制实例化进程,类就在改变实例化进程上有相应的伸缩性。
②由于在系统内存中只存在一个对象,因此可以节约系统资源,当需要频繁创建和销毁的对象时单例模式无疑可以提高系统的性能。
③避免对共享资源的多重占用。
缺点:
①不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态。
②由于单利模式中没有抽象层,因此单例类的扩展有很大的困难。
③单例类的职责过重,在一定程度上违背了“单一职责原则”。
适用场景:
单例模式只允许创建一个对象,因此节省内存,加快对象访问速度,因此对象需要被公用的场合适合使用,如多个模块使用同一个数据源连接对象等等。
结构型模式
适配器模式
将一个接口转换成客户希望的另一个接口,使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。
优点:
①可以在不修改原有代码的基础上来复用现有类,很好地符合 “开闭原则”(这点是两种实现方式都具有的)
②采用 “对象组合”的方式,更符合松耦合。
缺点:
①使得重定义Adaptee的行为较困难,这就需要生成Adaptee的子类并且使得Adapter引用这个子类而不是引用Adaptee本身。
适用场景:
①系统需要复用现有类,而该类的接口不符合系统的需求
②想要建立一个可重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。
③对于对象适配器模式,在设计里需要改变多个已有子类的接口,如果使用类的适配器模式,就要针对每一个子类做一个适配器,而这不太实际。
桥接模式
将抽象部分与它的实现部分分离,使它们都可以独立地变化。它是一种对象结构型模式,又称为柄体(Handle and Body)模式或接口(Interface)模式。
优点:
①把抽象接口与其实现解耦。
②抽象和实现可以独立扩展,不会影响到对方。
③实现细节对客户透明,对用于隐藏了具体实现细节。
缺点:
增加了系统的复杂度
使用场景:
①如果一个系统需要在构件的抽象化角色和具体化角色之间添加更多的灵活性,避免在两个层次之间建立静态的联系。
②设计要求实现化角色的任何改变不应当影响客户端,或者实现化角色的改变对客户端是完全透明的。
③需要跨越多个平台的图形和窗口系统上。
④一个类存在两个独立变化的维度,且两个维度都需要进行扩展。
组合模式
组合多个对象形成树形结构以表示具有“整体—部分”关系的层次结构。组合模式对单个对象(即叶子对象)和组合对象(即容器对象)的使用具有一致性,组合模式又可以称为“整体—部分”(Part-Whole)模式,它是一种对象结构型模式。
优点:
①组合模式使得客户端代码可以一致地处理对象和对象容器,无需关系处理的单个对象,还是组合的对象容器。
②将”客户代码与复杂的对象容器结构“解耦。
③可以更容易地往组合对象中加入新的构件。
缺点:
使得设计更加复杂。客户端需要花更多时间理清类之间的层次关系。(这个是几乎所有设计模式所面临的问题)。
在以下情况下应该考虑使用组合模式:
①需要表示一个对象整体或部分的层次结构。
②希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。
装饰模式
动态地给一个对象增加一些额外的职责,就增加对象功能来说,装饰模式比生成子类实现更为灵活。装饰模式是一种对象结构型模式。
优点:
①装饰这模式和继承的目的都是扩展对象的功能,但装饰者模式比继承更灵活
②通过使用不同的具体装饰类以及这些类的排列组合,设计师可以创造出很多不同行为的组合
③装饰者模式有很好地可扩展性
缺点:
①装饰者模式会导致设计中出现许多小对象,如果过度使用,会让程序变的更复杂。并且更多的对象会是的差错变得困难,特别是这些对象看上去都很像。
使用场景:
①需要扩展一个类的功能或给一个类增加附加责任。
②需要动态地给一个对象增加功能,这些功能可以再动态地撤销。
③需要增加由一些基本功能的排列组合而产生的非常大量的功能
外观模式
为子系统中的一组接口提供一个统一的入口。外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
优点:
①外观模式对客户屏蔽了子系统组件,从而简化了接口,减少了客户处理的对象数目并使子系统的使用更加简单。
②外观模式实现了子系统与客户之间的松耦合关系,而子系统内部的功能组件是紧耦合的。松耦合使得子系统的组件变化不会影响到它的客户。
缺点:
①如果增加新的子系统可能需要修改外观类或客户端的源代码,这样就违背了”开——闭原则“(不过这点也是不可避免)。
使用场景:
①外一个复杂的子系统提供一个简单的接口
②提供子系统的独立性
③在层次化结构中,可以使用外观模式定义系统中每一层的入口。其中三层架构就是这样的一个例子。
享元模式
运用共享技术有效的支持大量细粒度的对象。
享元模式优点就在于它能够大幅度的降低内存中对象的数量;而为了做到这一步也带来了它的缺点:它使得系统逻辑复杂化,而且在一定程度上外蕴状态影响了系统的速度。
使用场景:
①一个系统中有大量的对象,这些对象耗费大量的内存,这些对象中的状态大部分都可以被外部化。
②这些对象可以按照内部状态分成很多的组,当把外部对象从对象中剔除时,每一个组都可以仅用一个对象代替
③软件系统不依赖这些对象的身份,
代理模式
给某一个对象提供一个代理或占位符,并由代理对象来控制对原对象的访问。
优点:
①代理模式能够将调用用于真正被调用的对象隔离,在一定程度上降低了系统的耦合度;
②代理对象在客户端和目标对象之间起到一个中介的作用,这样可以起到对目标对象的保护。代理对象可以在对目标对象发出请求之前进行一个额外的操作,例如权限检查等。
缺点:
①由于在客户端和真实主题之间增加了一个代理对象,所以会造成请求的处理速度变慢
②实现代理类也需要额外的工作,从而增加了系统的实现复杂度。
使用场景:
①远程代理,也就是为一个对象在不同的地址空间提供局部代表。这样可以隐藏一个对象存在于不同地址空间的事实。
②虚拟代理,是根据需要创建开销很大的对象。通过它来存放实例化需要很长世间的真实对象。
③安全代理,用来控制真是对象访问时的权限。
④智能指引,是指当调用真实的对象时,代理处理另外一些事。
下一篇: 面向对象的设计原则