再谈设计模式—模式23种设计模式总结
Author:[email protected] Date:
这篇文章是对设计模式的再谈系列总结性笔记,推荐阅读C语言中文网上的《软件设计模式概述》
什么是设计模式
软件设计模式(Software Design Pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。
它描述了在软件设计过程中的一些不断重复发生的问题,以及该问题的解决方案。也就是说,它是解决特定问题的一系列套路,是前辈们的代码设计经验的总结,具有一定的普遍性,可以反复使用。其目的是为了提高代码的可重用性、代码的可读性和代码的可靠性。
设计模式的本质是面向对象设计原则的实际运用,是对类的封装性、继承性和多态性以及类的关联关系和组合关系的充分理解。正确使用设计模式具有以下优点:
可以提高程序员的思维能力、编程能力和设计能力。
使程序设计更加标准化、代码编制更加工程化,使软件开发效率大大提高,从而缩短软件的开发周期。
使设计的代码可重用性高、可读性强、可靠性高、灵活性好、可维护性强。
设计模式有两种分类方法,即根据模式的目的来分和根据模式的作用的范围来分。
设计模式的分类
根据目的来分:即根据模式是用来完成什么工作来划分,这种方式可分为创建型模式、结构型模式和行为型模式 3 种。
创建型模式:对象实例化的模式,创建型模式用于解耦对象的实例化过程。描述“怎样创建对象”,它的主要特点是“将对象的创建与使用分离”
共五种:单例模式、原型模式、工厂方法模式、抽象工厂模式、建造者模式。唯有工厂方法模式属于类创建型模式
结构型模式:把类或对象结合在一起形成一个更大的结构。用于描述如何将类或对象按某种布局组成更大的结构
共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式:类和对象如何交互,及划分责任和算法。描述类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,以及怎样分配职责。
共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
根据作用范围来分:即根据模式是主要用于类上还是主要用于对象上来分,这种方式可分为类模式和对象模式两种。
类模式:用于处理类与子类之间的关系,这些关系通过继承来建立,是静态的,在编译时刻便确定下来了。
共四种:工厂方法、(类)适配器、模板方法、解释器。
对象模式:用于处理对象之间的关系,这些关系可以通过组合或聚合来实现,在运行时刻是可以变化的,更具动态性。
GoF 中除了以上 4 种,其他的都是对象模式。
23 种设计模式的分类表
范围\目的 | 创建型模式 | 结构型模式 | 行为型模式 |
---|---|---|---|
类模式 | 工厂方法 | (类)适配器 | 模板方法、解释器 |
对象模式 | 单例 原型 抽象工厂 建造者 | 代理 (对象)适配器 桥接 装饰 外观 享元 组合 | 策略 命令 职责链 状态 观察者 中介者 迭代器 访问者 备忘录 |
GoF的23种软件设计模式列表
单例模式(Singleton pattern):某个类只能有一个实例,提供一个全局的访问点。
工厂方法(factory method pattern):定义一个创建对象的接口,让子类决定实例化那个类。
抽象工厂(Abstract factory pattern):创建相关或依赖对象的家族,而无需明确指定具体类。
建造者模式(Builder pattern):封装一个复杂对象的构建过程,并可以按步骤构造。
原型模式(prototype pattern):通过复制现有的实例来创建新的实例。
适配器模式(Adapter pattern):将一个类的方法接口转换成客户希望的另外一个接口。
组合模式(composite pattern):将对象组合成树形结构以表示“”部分-整体“”的层次结构。
装饰模式(decorator pattern):动态的给对象添加新的功能。
代理模式(Proxy pattern):为其他对象提供一个代理以便控制这个对象的访问。
亨元(蝇量)模式(Flyweight Pattern):通过共享技术来有效的支持大量细粒度的对象。
外观模式(facade pattern):对外提供一个统一的方法,来访问子系统中的一群接口。
桥接模式(Bridge pattern):将抽象部分和它的实现部分分离,使它们都可以独立的变化。
模板模式(Template pattern):定义一个算法结构,而将一些步骤延迟到子类实现。
解释器模式(Interpreter pattern):给定一个语言,定义它的文法的一种表示,并定义一个解释器。
策略模式(strategy pattern):定义一系列算法,把他们封装起来,并且使它们可以相互替换。
状态模式(State pattern):允许一个对象在其对象内部状态改变时改变它的行为。
观察者模式(observer pattern):对象间的一对多的依赖关系。
备忘录模式(Memento pattern):在不破坏封装的前提下,保持对象的内部状态。
中介者模式(Mediator pattern):用一个中介对象来封装一系列的对象交互。
命令模式(Command pattern):将命令请求封装为一个对象,使得可以用不同的请求来进行参数化。
访问者模式(visitor pattern):在不改变数据结构的前提下,增加作用于一组对象元素的新功能。
责任链模式(Chain of responsibility pattern):将请求的发送者和接收者解耦,使的多个对象都有处理这个请求的机会。
迭代器模式(iterator pattern):一种遍历访问聚合对象中各个元素的方法,不暴露该对象的内部结构。
其实还有两类:并发型模式和线程池模式。
概说24种设计模式
在程序开发中,有些对象我们只需要一个,比如说:线程池、对话框、缓存、唯一的序列号生成器、有状态的工具类对象等。如果我们实例化多个对象就可能会出错。那么怎样才能保证一个类只有一个实例化对象呢?
这里我们想到了全局变量,全局变量确实是可以。保证该类可以随时访问,但是它很难解决只有一个实例问题。
最好的办法就是让该自身来负责保存它的唯一实例。这个类必须要保证没有其他类来创建它。这里我们可以将其构造方法私有化。
单例模式(Singleton pattern)
确保某一个类只有一个实例,并且提供一个全局访问点。就是保证一个类仅有一个实例即可「new 一次」
单例模式具备典型的3个特点:1、只有一个实例。 2、自我实例化。 3、提供全局访问点。
因此当系统中只需要一个实例对象或者系统中只允许一个公共访问点,除了这个公共访问点外,不能通过其他访问点访问该实例时,可以使用单例模式。
单例模式的优缺点:
单例模式的主要优点就是节约系统资源、提高了系统效率,同时也能够严格控制客户对它的访问。也许就是因为系统中只有一个实例,这样就导致了单例类的职责过重,违背了“单一职责原则”,同时也没有抽象类,所以扩展起来有一定的困难。
Gson、Retrofit对象,ServiceManager等,Android系统提供的各种XXXManager(NotificationManager)等等,这些通过单例的方式去管理它,能够让你业务设计的更加严谨
单例模式的优点
1.在单例模式中,活动的单例只有一个实例,对单例类的所有实例化得到的都是相同的一个实例。这样就 防止其它对象对自己的实例化,确保所有的对象都访问一个实例
2.单例模式具有一定的伸缩性,类自己来控制实例化进程,类就在改变实例化进程上有相应的伸缩性。
3.提供了对唯一实例的受控访问。
4.由于在系统内存中只存在一个对象,因此可以 节约系统资源,当需要频繁创建和销毁的对象时单例模式无疑可以提高系统的性能。
5.允许可变数目的实例。
6.避免对共享资源的多重占用。
单例模式的缺点:
1.不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态。
2.由于单利模式中没有抽象层,因此单例类的扩展有很大的困难。
3.单例类的职责过重,在一定程度上违背了“单一职责原则”。
4.滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。
使用注意事项:
1.使用时不能用反射模式创建单例,否则会实例化一个新的对象
2.使用懒单例模式时注意线程安全问题
3.单例模式和懒单例模式构造方法都是私有的,因而是不能被继承的,有些单例模式可以被继承(如登记式模式)
单例模式的模式实现
由类是否在被加载时就将自己实例化,可以将单例模式分为两种:
饿汉式
在被引用的时候就被实例化。类一旦加载就创建一个单例,保证在调用 getInstance 方法之前单例已经存在了,「把这一过程当作一个汉堡,也就是说必须要把汉堡提前准备好,饿货就知道吃」
饿汉式特点:是线程安全的,类不是延时加载「直接是类加载的时候就初始化」
饿汉式优点:没有加锁,执行效率非常高「其实是以空间来换时间」
饿汉式缺点:在类加载的时候就会初始化,浪费内存「你知道我要不要使用这个实例吗,你就给我初始化,太任性了」
懒汉式
在需要的时候才被实例化。类加载时没有生成单例,只有当第一次调用 getlnstance 方法时才去创建这个单例
懒汉模式特点:线程不安全,延时初始化类,在我需要的时候「也就调用 getInstance」的时候才去初始化
懒汉式优点:延时初始化类,省资源,不想用的时候就不会浪费内存
懒汉式缺点:线程不安全,多线程操作就会有问题;加锁虽然线程安全,但对性能影响非常大「相当于排队获取资源,没有拿到锁子就干等」,双重加锁(双重检查锁:double-checked locking)多线程仿问性能达到提升,但是双检锁会遇到指令重排的问题,导致多线程下失效
推进阅读《人人都会设计模式--单例模式--Singleton》
js代码实现:
class Single { getSingle(fn) { var result; //由于闭包,所以当single.getSingle函数执行完之后,内存中并不会销毁result。 return function() { //单例模式的主要思想就是,实例如果已经创建,则直接返回 return result || (result = fn.apply(this, arguments)) } } }
java代码实现
public class SingletonTest { private static SingletonTest instance = null; private Vector properties = null; public Vector getProperties() { return properties; } private SingletonTest() { } private static synchronized void syncInit() { if (instance == null) { instance = new SingletonTest(); } } public static SingletonTest getInstance() { if (instance == null) { syncInit(); } return instance; } public void updateProperties() { SingletonTest shadow = new SingletonTest(); properties = shadow.getProperties(); } }
单例模式的应用场景
在应用场景中,某类只要求生成一个对象的时候,如一个班中的班长、每个人的身份证号等。
当对象需要被共享的场合。由于单例模式只允许创建一个对象,共享该对象可以节省内存,并加快对象访问速度。如 Web 中的配置对象、数据库的连接池等。
当某类需要频繁实例化,而创建的对象又频繁被销毁的时候,如多线程的线程池、网络连接池等。
原型模式
用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。在这里,原型实例指定了要创建的对象的种类。用这种方式创建对象非常高效,根本无须知道对象创建的细节。例如,JavaScript就是基于原型模式,由于 Java 提供了对象的 clone() 方法,所以用 Java 实现原型模式很简单。
原型模式的结构
原型模式包含以下主要角色。
抽象原型类:规定了具体原型对象必须实现的接口。
具体原型类:实现抽象原型类的 clone() 方法,它是可被复制的对象。
访问类:使用具体原型类中的 clone() 方法来复制新的对象。
模式的实现
原型模式的克隆分为浅克隆和深克隆,Java 中的 Object 类提供了浅克隆的 clone() 方法,具体原型类只要实现 Cloneable 接口就可实现对象的浅克隆
//具体原型类 class Realizetype implements Cloneable { Realizetype() { System.out.println("具体原型创建成功!"); } public Object clone() throws CloneNotSupportedException { System.out.println("具体原型复制成功!"); return (Realizetype)super.clone(); } } //原型模式的测试类 public class PrototypeTest { public static void main(String[] args)throws CloneNotSupportedException { Realizetype obj1=new Realizetype(); Realizetype obj2=(Realizetype)obj1.clone(); System.out.println("obj1==obj2?"+(obj1==obj2)); } }
对象深、浅复制的概念:
浅复制:将一个对象复制后,基本数据类型的变量都会重新创建,而引用类型,指向的还是原对象所指向的。
深复制:将一个对象复制后,不论是基本数据类型还有引用类型,都是重新创建的。简单来说,就是深复制进行了完全彻底的复制,而浅复制不彻底。
public class Prototype implements Cloneable, Serializable { private static final long serialVersionUID = 1L; private String string; private SerializableObject obj; /* 浅复制 */ public Object clone() throws CloneNotSupportedException { Prototype proto = (Prototype) super.clone(); return proto; } /* 深复制 */ public Object deepClone() throws IOException, ClassNotFoundException { /* 写入当前对象的二进制流 */ ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(this); /* 读出二进制流产生的新对象 */ ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); return ois.readObject(); } public String getString() { return string; } public void setString(String string) { this.string = string; } public SerializableObject getObj() { return obj; } public void setObj(SerializableObject obj) { this.obj = obj; } } class SerializableObject implements Serializable { private static final long serialVersionUID = 1L; }
要实现深复制,需要采用流的形式读入当前对象的二进制输入,再写出二进制数据对应的对象。
工厂设计模式
我们把被创建的对象称为“产品”,把创建产品的对象称为“工厂”。如果要创建的产品不多,只要一个工厂类就可以完成,这种模式叫“简单工厂模式”,它不属于 GoF 的 23 种经典设计模式,它的缺点是增加新产品时会违背“开闭原则”。
麦当劳和肯德基就是生产鸡翅的Factory 工厂模式:客户类和工厂类分开。消费者任何时候需要某种产品,只需向工厂请求即可。消费者无须修改就可以接纳新产品。缺点是当产品修改时,工厂类也要做相应的修改。如:如何创建及如何向客户端提供。
简单工厂一般分为:
普通简单工厂:就是建立一个工厂类,对实现了同一接口的一些类进行实例的创建
多方法简单工厂:提供多个工厂方法,分别创建对象
静态方法简单工厂:将上面的多个工厂方法模式里的方法置为静态的,不需要创建实例,直接调用即可
工厂模式适合:凡是出现了大量的产品需要创建,并且具有共同的接口时,可以通过工厂方法模式进行创建。我们会选用静态工厂方法模式——不需要实例化工厂类。
工厂模式简而言之,就是要替代掉“new操作符”!
为什么需要替代new操作符?
因为有时候创建实例时需要大量的准备工作,而将这些准备工作全部放在构造函数中是非常危险的行为,有必要将创建实例的逻辑和使用实例的逻辑分开,方便以后扩展。
工厂模式js实现:
class Creator { create(name) { return new Animal(name) } } class Animal { constructor(name) { this.name = name } } var creator = new Creator() var duck = creator.create('Duck');console.log(duck.name) // Duck var chicken = creator.create('Chicken');console.log(chicken.name) // Chicken
一个工程模式优化例子
class People { constructor(des) { // 出现异步不能使用async await // 函数调用时可能还未完成初始化 get('someUrl').then(data => { this.name = data.name get('someUrl?name=' + this.name).then(data => { this.age = data.age }) }) // 非成员函数耦合性变大 this.des = handleDes(des) }}
工产模式
// 还真别说,形式上好看的代码,质量一般都比较高class People { name: string = '' age: number = 0 des: string = '' constructor(name: string, age: number, des: string) { this.name = name this.age = age this.des = des }}async function peopleFactory(description:any){ const name = await get('someUrl') const age = await get('someUrl?name='+name) const des = handle(description) return new People(name,age,des)}
工厂方法模式的主要优点有:
用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程;
在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改,满足开闭原则;、
工厂方法模式缺点是:每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度。
工厂方法模式(Factory Method)
简单工厂模式有一个问题就是,类的创建依赖工厂类,也就是说,如果想要拓展程序,必须对工厂类进行修改,这违背了开闭原则,所以,从设计角度考虑,有一定的问题,如何解决?就用到工厂方法模式:
创建一个工厂接口和创建多个工厂实现类,这样一旦需要增加新的功能,直接增加新的工厂类就可以了,不需要修改之前的代码。
工厂方法模式:核心工厂类不再负责所有产品的创建,而是将具体创建的工作交给子类去做,成为一个抽象工厂角色,仅负责给出具体工厂类必须实现的接口,而不接触哪一个产品类应当被实例化这种细节。
请MM去麦当劳吃汉堡,不同的MM有不同的口味,要每个都记住是一件烦人的事情,我一般采用Factory Method模式,带着MM到服务员那儿,说“要一个汉堡”,具体要什么样的汉堡呢,让MM直接跟服务员说就行了。
实现方式:
a) 抽象产品类(也可以是接口)
b) 多个具体的产品类
c) 工厂类(包括创建a的实例的方法)
抽象工厂(Abstract factory pattern)
抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品。
系统中有多个产品族,每个具体工厂创建同一族但属于不同等级结构的产品。
系统一次只可能消费其中某一族产品,即同族的产品一起使用。
抽象工厂模式的结构
抽象工厂模式同工厂方法模式一样,也是由抽象工厂、具体工厂、抽象产品和具体产品等 4 个要素构成,但抽象工厂中方法个数不同,抽象产品的个数也不同。
抽象工厂模式的主要角色如下。
抽象工厂(Abstract Factory):提供了创建产品的接口,它包含多个创建产品的方法 newProduct(),可以创建多个不同等级的产品。
具体工厂(Concrete Factory):主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。
抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它 同具体工厂之间是多对一的关系。
工厂方法模式和抽象工厂模式不好分清楚,他们的区别如下:
工厂方法模式:
一个抽象产品类,可以派生出多个具体产品类。
一个抽象工厂类,可以派生出多个具体工厂类。
每个具体工厂类只能创建一个具体产品类的实例。
抽象工厂模式:
多个抽象产品类,每个抽象产品类可以派生出多个具体产品类。
一个抽象工厂类,可以派生出多个具体工厂类。
每个具体工厂类可以创建多个具体产品类的实例,也就是创建的是一个产品线下的多个产品。
工厂方法模式和抽象工厂模的区别:
工厂方法模式只有一个抽象产品类,而抽象工厂模式有多个。
工厂方法模式的具体工厂类只能创建一个具体产品类的实例,而抽象工厂模式可以创建多个。
工厂方法创建 "一种" 产品,他的着重点在于"怎么创建",也就是说如果你开发,你的大量代码很可能围绕着这种产品的构造,初始化这些细节上面。也因为如此,类似的产品之间有很多可以复用的特征,所以会和模版方法相随。
抽象工厂需要创建一些列产品,着重点在于"创建哪些"产品上,也就是说,如果你开发,你的主要任务是划分不同差异的产品线,并且尽量保持每条产品线接口一致,从而可以从同一个抽象工厂继承。
建造者模式
建造者模式将一个复杂对象的构建与表示分离,使得同样的构建过程可以创建不同的表示。
如果我们用了建造者模式,那么用户就只需要指定需要建造的类型就可以得到他们,而具体的过程以及细节就不需要知道。
例如,计算机是由 OPU、主板、内存、硬盘、显卡、机箱、显示器、键盘、鼠标等部件组装而成的,采购员不可能自己去组装计算机,而是将计算机的配置要求告诉计算机销售公司,计算机销售公司安排技术人员去组装计算机,然后再交给要买计算机的采购员。
建造者返回给客户一个完整的的产品对象,而客户端无须关心该对象所包含的额属性和组建方式,这就是建造者模式的设计动机。
建造者模式的结构
建造者(Builder)模式的主要角色如下。
产品角色(Product):它是包含多个组成部件的复杂对象,由具体建造者来创建其各个滅部件。
抽象建造者(Builder):它是一个包含创建产品各个子部件的抽象方法的接口,通常还包含一个返回复杂产品的方法 getResult()。
具体建造者(Concrete Builder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。
指挥者(Director):它调用建造者对象中的部件构造与装配方法完成复杂对象的创建,在指挥者中不涉及具体产品的信息。
abstract class Builder { public abstract void BuildPartA(); public abstract void BuildPartB(); public abstract Product GetResult(); } public class Director { public void Construct(Builder builder) { builder.BuildPartA(); builder.BuildPartB(); } } public class Product { List parts = new ArrayList(); public void Add(String part) { parts.add(part); } public void Show() { for (String string : parts) { //具体操作 } } } public class ConcreteBuiler1 extends Builder { private Product product = new Product(); @Override public void BuildPartA() { // 具体操作 } @Override public void BuildPartB() { // 具体操作 } @Override public Product GetResult() { return product; } } public class ConcreteBuiler2 extends Builder { private Product product = new Product(); @Override public void BuildPartA() { // 具体操作 } @Override public void BuildPartB() { // 具体操作 } @Override public Product GetResult() { return product; } } public class Main { public static void main(String[] args) { Director director = new Director(); Builder builder1 = new ConcreteBuiler1(); Builder builder2 = new ConcreteBuiler2(); director.Construct(builder1); Product product1 = builder1.GetResult(); product1.Show(); director.Construct(builder2); Product product = builder2.GetResult(); product.Show(); } } --------------------- 作者:六小聪 来源:CSDN 原文:https://blog.csdn.net/DBC_121/article/details/86488568 版权声明:本文为博主原创文章,转载请附上博文链接!
抽象工厂模式VS建造者模式的区别
我们可以将固定的逻辑操作抽象出来(Builder),然后在不同形状的人偶实现不同的具体类(ConcreteBuilder),并为了保证依赖倒置原则,再加一个指挥生产的类,是建造过程对客户端封闭。可能这个时候就有人会问,那这不就像是工厂模式么,抽象不同的的操作,使用工厂类生产。但是,我们要注意到,工厂模式其实就是封装了new的“调用动作(new XXX)”,使客户端不用按服务端的规定显式的指定new什么,而是通过更高抽象的create函数并导入客户端的规定来new出服务端的东西。建造者模式则是封装了new的“实现定义(以组合的角度来确定其结构)”,使客户端不用去接触new里复杂的组合步骤。
摘选文字:
设计模式 https://blog.csdn.net/m0_37691414/column/info/21939
JavaScript中常用的设计模式 https://segmentfault.com/a/1190000017787537?utm_source=tag-newest
深入理解JavaScript系列/设计模式--汤姆大叔的博客
设计模式--菜鸟教程
JavaScript 中常见设计模式整理
设计模式分类 https://www.cnblogs.com/tonglingliangyong/p/3738761.html
23种设计模式的趣味理解 https://www.cnblogs.com/hegx/p/6094604.html
转载本站文章《再谈设计模式—模式23种设计模式总结》,
请注明出处:https://www.zhoulujun.cn/html/theory/engineering/model/6191.html