设计模式的概念
设计模式:Design Pattern,DP
软件设计模式(Software Design Pattern),又称设计模式,它描述了在软件设计过程中的一些不断重复发生的问题,以及该问题的解决方案。也就是说,它是解决特定问题的一系列套路,是前辈们的代码设计经验的总结,具有一定的普遍性,可以反复使用。其目的是为了提高代码的可重用性、代码的可读性和代码的可靠性。
1.设计模式的六大设计原则
1.1 开闭原则:OCP
1.1.1 定义
开闭原则:Open Closed Principle,OCP
- 对扩展开放,对修改关闭。
简单点说,就是:一个软件实体应该通过扩展来实现变化,而不是通过修改已有的代码来实现变化。
这里的软件实体包括以下几个部分:
- 项目中划分出的模块
- 类与接口
- 方法
一个软件产品在它的生命周期内一般都会发生变化,开闭原则是为软件实体的未来事件而制定的对现行开发设计进行约束的一个原则。
1.1.2 开闭原则的作用
开闭原则是面向对象程序设计的终极目标,它使软件实体拥有一定的适应性和灵活性的同时具备稳定性和延续性。具体来说,其作用如下:
-
对软件测试的影响
软件遵守开闭原则的话,软件测试时只需要对扩展的代码进行测试就可以了,因为原有的测试代码仍然能够正常运行。
-
可以提高代码的复用性
粒度越小,被复用的可能性就越大;
在面向对象的程序设计中,根据原子和抽象编程可以提高代码的可复用性。
-
可以提高软件的可维护性
遵守开闭原则的软件,其稳定性高和延续性强,从而易于扩展和维护。
1.2 单一职责原则:SRP
1.2.1 定义
单一职责原则(Single Responsibility Principle,SRP)又称单一功能原则。
单一职责原则规定一个类应该有且仅有引起它变化的原因,否则应该被拆分
1.2.1 单一职责原则的优点
单一职责原则的核心就是控制类的粒度大小、将对象解耦、提高其内聚性。
优点:
- 降低类的复杂度
- 一个类只负责一项职责,其逻辑肯定要比负责多项职责简单得多
- 提高类的可读性
- 复杂性降低,自然其可读性就会提高
- 提高系统的可维护性
- 可读性提高,自然更容易维护
- 变更引起的风险降低
- 变更是必然的,如果单一职责原则遵守得好,当修改一个功能时,可以显著降低对其他功能的影响
单一职责同样也适用于方法。一个方法应该尽可能做好一件事情。如果一个方法处理的事情太多,其粒度会变得很粗,不利于重用
1.3 里氏替换原则:LSP
里氏替换原则:Liskov Sunstitution Principle
该原则可以理解为:子类可以替换父类
1.3.1 定义
- 第一种:If for each object o1 of type S there is an object o2 of type T such that for all programs P defifined in terms of T, the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T.
- 如果每一种类型S的对象o1,都有一个类型T的对象o2,在以T定义的所有程序P中将所有的对象o2都替换为o1,而程序P的行为没有发生变化,那么S是T的子类
- 第二种:Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.
- 所有引用基类的地方必须能透明地使用其子类对象
里氏替换原则是继承复用的基石,它为良好的继承定义了一个规范,定义中包含了4层含义:
-
子类必须完全实现父类的方法
- 以前做过的项目中,经常定义一个接口或者抽象类,然后编码实现,调用类则直接传入接口或者抽象类,其实这就是已经在使用里氏替换原则了。
-
子类中可以增加自己特有的方法
-
当子类覆盖或实现父类的方法时,方法的输入参数(方法的形参)要比父类方法的输入参数更宽松
这里字类并非重写了父类的方法,而是重载了父类的方法。因为子类和父类的方法的输入参数是不同的。 子类方法的参数比父类方法的参数的范围要大,所以当参数输入为父类参数类型时,只会执行父类的方法,不会执行父类的重载方法。
-
当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格
1.3.2 里氏替换原则的作用
主要作用如下:
- 里氏替换原则是实现开闭原则的重要方式之一
- 它克服了继承中重写父类造成的可复用性变差的缺点
- 它是动作正确性的保证。即类的扩展不会给已有的系统引入新的错误,降低了代码出错的可能性
里氏替换原则通俗来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。也就是说:子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。
如果通过重写父类的方法来完成新的功能,这样写起来虽然简单,但是整个继承体系的可复用性会比较差,特别是运用多态比较频繁时,程序运行出错的概率会非常大。
如果违背了里氏替换原则,则继承类的对象在基类出现的地方会出现运行错误。这时其修正方法是:取消原来的继承关系,重新设计它们之间的关系。
1.4 依赖倒置原则:DIP
依赖倒置原则:Dependence Inversion Principle
1.4.1 定义
**初始定义:**High level modules shouldnot depend upon low level modules.Both should depend upon abstractions.Abstractions should not depend upon details. Details should depend upon abstractions。
里面包含了三层含义:
- 高层模块不应该依赖底层模块,两者都应该依赖其抽象
- 抽象不应该依赖细节
- 细节应该依赖抽象
核心思想:要面向接口编程,不要面向实现编程。
依赖倒置原则是实现开闭原则的重要途径之一,它降低了客户与实现模块之间的耦合。
由于在软件设计中,细节具有多变性,而抽象层则相对稳定,因此以抽象为基础搭建起来的架构要比以细节为基础搭建起来的架构要稳定得多。这里的抽象指的是接口或者抽象类,而细节是指具体的实现类。
使用接口或者抽象类的目的是制定好规范和契约,而不去涉及任何具体的操作,把展现细节的任务交给它们的实现类去完成。
1.4.2 依赖倒置原则的作用
- 依赖倒置原则可以降低类间的耦合性
- 依赖倒置原则可以提高系统的稳定性
- 依赖倒置原则可以减少并行开发引起的风险
- 依赖倒置原则可以提高代码的可读性和可维护性
1.4.3 依赖倒置原则的实现方法
依赖倒置原则的目的是通过要面向接口的编程来降低类间的耦合性,所以我们在实际编程中只要遵循以下几点,就能在项目中满足这个规则。
- 每个类尽量提供接口或抽象类,或者两者都具备
- 变量的声明类型尽量是接口或者是抽象类
- 任何类都不应该从具体类派生
- 尽量不要覆写基类的方法
- 使用继承时结合里氏替换原则
1.5 接口隔离原则:ISP
接口隔离原则:Interface Segregation Principle
1.5.1 定义
接口隔离原则要求程序员尽量将臃肿庞大的接口拆分成更小的和更具体的接口,让接口中只包含客户感兴趣的方法。
罗伯特·C·马丁给”接口隔离原则“的定义是:客户端不应该被迫依赖于它不实用的方法(Clients should not be forced to depend on methods they do not use)。该原则还有另外一个定义:一个类对另一个类的依赖应该建立在最小的接口上(The dependency of one class to another one should depend on the smallest possible interface)。
以上两个定义的含义是:要为各个类建立它们需要的专用接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。
接口隔离原则和单一职责原则都是为了提高类的内聚性、降低它们之间的耦合性,体现了封装的思想,但两者是不同的:
- 单一职责原则注重的是职责,而接口隔离原则注重的是对接口依赖的隔离。
- 单一职责原则主要是约束类,它针对的是程序中的实现和细节;接口隔离原则主要约束接口,主要针对抽象和程序整体框架的构建。
1.5.2 接口隔离原则的优点
接口隔离原则是为了约束接口、降低类对接口的依赖性,遵循接口隔离原则有以下几点:
- 将臃肿庞大的接口分为多个粒度小的接口,可以预防外来变更的扩散,提高系统的灵活性和可维护性。
- 接口隔离提高了系统的内聚性,减少了对外交互,降低了系统的耦合性。
- 如果接口的粒度大小定义合理,能够保证系统的稳定性;但是,如果定义过小,会造成接口数量过多,使设计复杂化;如果定义太大,灵活性降低,无法提供定制服务,实现对总接口的定义。
- 使用多个专门的接口还能够体现对象的层次,因为可以通过接口的继承,实现对总接口的定义。
- 能减少项目工程中的代码冗余。过大的大接口里面通常放置许多不用的方法,当实现这个接口的时候,被迫设计冗余的代码。
1.5.3 接口隔离原则的实现方法
在具体应用接口隔离原则时,应该根据以下几个规则来衡量。
- 接口尽量小,但是要有限度。
- 一个接口只能服务于一个子模块或业务逻辑。
- 为依赖接口的类定制服务。
- 只提供调用者需要的方法,屏蔽不需要的方法。
- 了解环境,拒绝盲从。
- 每个项目或产品都有选定的环境因素,环境不同,接口拆分的标准就不同。
- 提高内聚,减少对外交互。
- 使接口用最少的方法去完成最多的事情。
1.6 迪米特法则:LoD
1.6.1 迪米特法则的定义
迪米特法则(Law of Demeter,LoD)又叫作最少知识原则(Least Knowledge Principle,LKP)。
它要求一个对象应该对其它对象有最少的了解。通俗的说,一个类应该对自己需要耦合或调用的类知道的最少,被耦合或调用的类的内部是如何复杂都与我无关,我就知道你提供的public方法。
迪米特法则还是在讲如何减少耦合的问题,类之间的耦合越弱,越有利于复用,一个处在弱耦合的类被修改,不会对有关系的类造成波及。也就是说,信息的隐藏促进了软件的复用。
迪米特法则还有一个定义是:至于你的直接朋友交谈,不跟”陌生人“说话(Talk only to your immediate friends and not to strangers.)。其含义是:如果两个软件尸体无需直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。
什么叫做直接朋友呢?每个对象都必然会和其他对象有耦合关系,两个对象之间的耦合就成为朋友关系,这种关系有很多比如组合、聚合、依赖等等。包括以下几类:
- 当前对象本身(this)
- 当前对象的方法参数(以参数形式传入到当前对象方法中的对象)
- 当前对象的成员对象
- 如果当前对象的成员对象是一个集合,那么集合中的元素也都是朋友
- 当前对象所创建的对象
1.6.2 迪米特法则的优点
- 降低了类之间的耦合度,提高了模块的相对独立性。
- 由于亲和度降低,从而提高了类的可复用率和系统的扩展性
但是,过度使用迪米特法则会使系统产生大量的中介类,从而增加系统的复杂性,使模块之间的通信效率降低。所以,在采用迪米特法则时需要反复权衡,确保高内聚和低耦合的同时,保证系统的结构清晰。
1.6.3 迪米特法则的实现方法
从迪米特法则的定义和特点可知,它强调以下两点:
- 从依赖着的角度来说,只依赖应该依赖的对象
- 从被依赖者的角度说,只暴露应该暴露的方法
所以,在运用迪米特法则时要注意以下几点:
- 在类的划分上,应该创建弱耦合的类。类与类之间的耦合越弱,就越有利于实现可复用的目标。
- 在类的结构设计上,尽量降低类成员的访问权限。
- 在类的设计上,优先考虑将一个类设置成不变类。
- 在对其他类的引用上,将引用其他对象的次数降到最低。
- 不暴露类的属性成员,而应该提供相应的访问器(set和get方法)。
- 谨慎使用序列化(Serializable)功能。
2. 设计模式之创建型模型
2.1 单例模式
单例:Singleton
2.1.1 单例模式的定义及特点
定义:
确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
Ensure a class has only one instance,and provide a global point of access to it.
特点:
- 单例类只有一个实例对象
- 该单例对象必须由单例类自行创建
- 单例类对外提供一个访问该单例的全局访问点
2.1.2 单例模式的分类
上面的单例模式,在低并发的情况下可能不会出现问题,如果并发量增大,内存中就会出现多个实例,就不是真正意义上的单例。
懒汉式单例:
该模式的特点是类加载时没有生成单例,只有当第一次调用getInstance方法时才去创建这个单例。
关键字volatile和synchronized,能保证线程安全,但是每次访问时都要同步,会影响性能,且消耗更多的资源,这是懒汉式单例的缺点。
饿汉式单例:
该模式的特点是类一旦加载就创建一个单例,保证在调用getInstance方法之前单例已经存在了。而且该方式是线程安全的。
2.1.3 单例模式的使用场景
- 某类只要求生成一个对象的时候,如一个航班的机长、每个人的身份证号等。
- 当对象需要被共享的场合。由于单例模式只允许创建一个对象,共享该对象可以节省内存,并加快对象访问速度。如Web中的配置对象、数据库的连接池等。
- 当某类需要频繁实例化,而创建的对象又频繁被销毁的时候,如多线程的线程池、网络连接池等。
- 在计算机系统中,Windows的回收站、操作系统中的文件系统、多线程中的线程池、显卡的驱动程序对象、打印机的后台处理服务、应用程序的日志对象、数据库的连接池、网站的计数器、Web应用的配置对象、应用程序中的对话框、系统中的缓存等常常被设计成单例。
2.1.4 单例模式的优缺点
优点
- 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例。
- 避免对资源的多重占用(比如写文件操作)。
- 单例模式可以在系统设置全局的访问点,优化和共享资源访问。
缺点
- 单例模式一般没有接口,扩展很困难。如果要扩展,只能修改代码。
- 与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
2.1.5 单例模式的扩展
单例模式可扩展为有限的多例(Multitcm)模式,这种模式可生成有限个实例并保存在ArmyList中,客户需要时刻随机获取。
2.2 工厂方法模式
2.2.1 工厂方法模式的定义
定义:
定义一个用户创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
工厂方法的主要角色如下:
- 抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法newProduct()来创建产品。
- 具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
- 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。
- 具体产品(ConcreteProduct)实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。
通用代码:
//抽象产品:提供了产品的接口
public interface Product{
public void method();
}
//具体的产品可以有多个,都实现抽象产品接口
public class ConcreteProduct1 implements Product{
public void method(){
//具体业务逻辑处理,例如
System.out.println("具体产品1显示...");
}
}
public class ConcreteProduct2 implements Product{
public void method(){
//具体业务逻辑处理,例如
System.out.println("具体产品2显示...");
}
}
//抽象工厂:负责定义产品对象的产生
public abstract class AbstractFactory{
//创建一个产品对象,输入的参数类型可以自行设置
public abstract <T extends Product>T createProduct(Class<T> tClass);
}
//具体工厂:具体如何生产一个产品的对象,是由具体的工厂类实现的
public class ConcreteFactory implements AbstractFactory{
public <T extends Product> T createProduct(Class<T> tClass) {
Product product=null;
try {
product(T)Class.forName(tClass.getName()).newInstance();
} catch (Exception e) {
//异常处理 }return (T)product;
}
}
//场景类:
public class Client {
public static void main(String[] args) {
AbstractFactory factory=new ConcreteFactory();
Product product=factory.createProduct(ConcreteProduct1.class);
//继续其他业务处理
}
}
2.2.3 工厂方法模式的应用场景
- 客户只知道创建产品的工厂名,而不知道具体的产品名。
- 创建对象的任务由多个具体子工厂中的某一个完成,而抽象工厂只提供创建产品的接口。
- 客户不关心创建产品的细节,只关心产品的品牌。
2.3 抽象工厂模式
2.3.1 工厂与抽象工厂简单比较
工厂方法模式只考虑生产同等级的产品,抽象工厂模式将考虑多等级产品的生产,将同一个具体工厂所生产的位于不同等级的一组产品称为一个产品族。
2.3.2 抽象工厂模式的定义与特点
抽象工厂(AbstractFactory)模式的定义:为创建一组相关或者相互依赖的对象提供一个接口,而无需指定他们的具体类。
抽象工厂模式是工厂方法模式的升级版本,在有多个业务品种、业务分类时,通过抽象工厂模式产生需要的对象一种非常好的解决方案。工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品。
使用抽象工厂模式需要满足以下条件:
- 系统中有多个产品族,每个具体工厂创建同一族但属于不同等级结构的产品。
- 系统一次只可能消费其中某一族产品,即同族的产品一起使用。
2.3.3 抽象工厂的通用代码
//抽象产品类(只写了一个AbstractProductA,AbstractProductB省略)
public abstract class AbstractProductA{
//每个产品的共有方法
public void sharedMthod(){}
//每个产品相同方法,不同实现
public abstract void doSomething();
}
//具体产品类
public class ProductA1 extends AbstractProductA{
public abstract void doSomething(){
System.out.println("产品A1的实现方法")
}
}
public class ProdectA2 extends AbstractProductA{
public abstract void doSomething(){
System.out.println("产品A2的实现方法")
}
}
//抽象工厂类:
public abstract class AbstractCreator{
//创建A产品家族
public abstract AbstractProductA createProductA();
//创建B产品家族
public abstract AbstractProductB createProductB();
//如果由N个产品组,该类中应该有N个创建方法
}
//产品等级实现类:
//有M个产品等级就应该有M个工厂的实现类,在每个实现工厂中,实现不同产品族的生产业务。
public class Creator1 extends AbstractCreator{
//只生成产品等级为1的A产品
public AbstractProductA createProductA(){
return new ProductA1();
}
//只生成产品等级为1的B产品
public AbstractProductB createProductB(){
return new ProductB1();
}
}
public class Creator2 extends AbstractCreator{
//只生成产品等级为2的A产品
public AbstractProductA createProductA(){
return new ProductA2();
}
//只生成产品等级为2的B产品
public AbstractProductB createProductB(){
return new ProductB2();
}
}
//场景类
public class Client{ public static void main(String[]] args){
//定义两个工厂
AbstractCreator creator1=new Creator1();
AbstractCreator creator2=new Creator2();
//产生A1对象
AbstractProductA a1=creator1.createProductA();
//产生A2对象
AbstractProductA a2=creator2.createProductA();
//产生B1对象
AbstractProductA a1=creator1.createProductB();
//产生B2对象
AbstractProductA a2=creator2.createProductB();
//按需求自己实现其他
}
2.3.4 抽象工厂模式的优缺点:
优点:
- 可以在类的内部对产品族中相关联的多等级产品共同管理,而不必专门引入多个新的类来进行管理。
- 当增加一个新的产品组是不需要修改原代码,满足开闭原则。
缺点:
- 当产品族中需要增加一个新的产品时,所有的工厂都需要进行修改。
2.3.5 抽象工厂模式的应用场景
- 适合用于产品之间相互关联、相互依赖且相互约束的地方
- 需要动态切换产品族的地方
2.4 建造者(Builder)模式
2.4.1 模式的定义与结构
定义
指将一个复杂对象的构造与它的表示分离,使同样的构建过程可以创建不同的表示,这样的设计模式被称为建造者模式。它是将一个复杂的对象分解为多个简单的对象,然后一步一步构建而成。它将变与不变分离,即产品的组成部分是不变的,但每一部分是可以灵活选择的。
模式的结构与实现
-
模式的结构
建造者模式的主要角色如下:
- 产品(Product)类:它是包含多个组成部件的复杂对象由具体建造者来创建其各个部件。
- 抽象建造者(Builder):它是一个包含创建产品各个子部件的抽象方法的接口,通常还包含一个返回复杂产品的方法getResult()。
- 具体建造者(Concrete Builder):实现Builder接口,完成复杂产品的各个部件的具体创建方法。
- 导演(Director)类:它调用建造者对象中的部件构造与装配方法完成复杂对象的创建,在指挥者中不涉及具体产品的信息。
-
模式的实现
-
产品类:包含多个组成部件的复杂对象
public class Product{ private String partA; private String partB; private String partC; public void setPartA(String partA){ this.partA = partA; } public void setPartB(String partB){ this.partB = partB; } public void setPartC(String partC){ this.partC = partC; } public void doSomething(){ //独立业务处理 } }
-
抽象建造者:包含创建产品各个子部件的抽象方法。
public abstract class Builder{ //创建产品的不同部分,以获取不同产品 public abstract void buildPartA(); public abstract void buildPartB(); public abstract void buildPartC(); //返回产品对象 public abstract Product buildProduct(); }
-
具体建造者:实现了抽象建造者接口。
public class ConcreteBuilder extends Builder{ private Product product = new Product(); public void buildPartA(){ product.setPartA("建造 PartA"); } public void buildPartB(){ product.setPartA("建造 PartB"); } public void buildPartC(){ product.setPartA("建造 PartC"); } //组建一个产品 public Product buildProduct(){ return product; } }
-
指挥者:调用建造者中的方法完成复杂对象的创建。
public class Director{ private Builder builder; public Director(Builder builder){ this.builder = builder; } //产品构建与组装方法:设置不同的零件,生成不同的产品 public Product constructA(){ builder.buildPartA(); builder.buildPartB(); builder.buildPartC(); return builder.buildProduct(); } public Product constructB(){ builder.builPartB(); builder.builPartA(); builder.builPartC(); return builder.buildProduct(); } }
-
场景类
public class Client{ public static void main(String[] args){ Builder builder = new ConcreteBuilder(); Director director = new Director(builder); Product product = director.construct(); product.doSomething(); } }
-
2.4.2 建造者模式的优缺点
优点:
- 各个具体的建造者相互独立,有利于系统的扩展
- 客户端不必知道产品内部组成的细节,便于控制细节风险
缺点:
- 产品的组成部分必须相同,限制了其使用范围
- 如果产品的内部变化复杂,该模式会增加很多的建造者类
建造者(Builder)模式和工厂模式的关注点不同:建造者模式注重零部件的组装过程,而工厂方法模式更注重零部件的创建过程,但两者可以结合使用。
2.4.3 模式的应用场景
建造者(Builder)模式创建的是复杂对象,其产品的各个部分经常面临着剧烈的变化,但将它们组合在一起的算法却相对稳定,所以它通常在以下场合使用:
- 相同的方法,不同的执行顺序,产生不同的实践结果
- 创建的对象那个较复杂,由多个部件构成,,各部件面临着复杂的变化,但构建间的建造顺序是稳定的
- 创建复杂对象的算法独立于该对象的组成部分以及他们的装配方式,即产品的构建过程和最终的表示是独立的
3. 设计模式之结构型模式
3.1 代理(Proxy)模式
3.1.1 代理模式的定义与结构
定义:
为其他对象提供一种代理以控制这个对象的访问。这是一个使用频率非常高的模式。
结构:
代理模式的结构比较简单,主要是通过定义一个继承抽象主题的代理来包含真实主题,从而实现对真实主题的访问。
1、代理模式的主要角色
- 抽象主题(Subject)角色:抽象主题类可以是接口或抽象类,是一个普通的业务类型定义,声明真实主题和代理对象实现的业务方法,无特殊要求。
- 真实主题(Real Subject)角色:真实主题角色类也叫做被委托角色、被代理角色,实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象,是业务逻辑的具体执行者。
- 代理(Proxy)角色:也叫委托类、代理类。它负责对真实角色的应用,把所有抽象类定义的方法限制委托给真实主题角色实现,并且在真实主题角色处理完毕前后做预处理和善后的工作。提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。
2、模式的实现
代码如下:
//抽象主题类
public interface Subject{
void request();
}
//真实主题类
public class RealSubject implements Subject{
public void request(){
//业务逻辑处理
}
}
//代理类:代理模式的核心就在代理类上
public class Proxy implements Subject{
//要代理哪个实现类
private Subject subject = null;
//通过构造方法传入被代理对象(也可以有其他方式)
public Proxy(Subject subject){
this.subject = subject;
}
public void request(){
preRequest();
resubjectalSubject.request();
postRequest();
}
//预处理
public void preRequest(){
System.out.println("访问真实主题之前的预处理。")
}
//善后工作
public void postResquest(){
System.out.println("访问真实主题之后的善后。")
}
}
//场景类
public class Client{
public static void main(String[] args){
Proxy proxy = new Proxy();
proxy.request();
}
}
一个代理类可以代理多个被委托或者被代理者,因此一个代理类具体代理哪个真实主题角色是由场景类决定的。最简单的情况就是一个主题类和一个代理类,这是最简单的代理模式。所以上面的结构中通过构造方法传入被代理对象就是最简单的方式。
3.1.2 代理模式的优缺点
优点:
- 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
- 代理对象可以扩展目标对象的功能;
- 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度。
缺点:
- 在客户端和目标对象之间增加了一个代理对象,会造成请求处理速度变慢;
- 增加了系统的复杂度。
3.1.3 代理模式的应用场景
在软件设计中,使用代理模式的例子有很多,例如:spring框架中就使用了动态代理模式
- 远程代理,这种方式通常是为了隐藏目标对象存在于不同地址空间的事实,方便客户端访问。
- 虚拟代理,这种方式通常用于要创建的目标对象开销很大时。
- 安全代理,这种方式通常用于控制不同种类客户对真实对象的访问权限。
- 智能指引,主要用于调用目标对象时,代理附加一些额外的处理功能。
- 延迟加载,指为了提高系统的性能,延迟对目标的加载。
3.2 适配器(Adapter)模式
3.2.1 适配器模式的定义与结构
定义
将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
适配器模式分为类结构型模式和对象结构性模式两种,前者类之间的耦合度比后者高,且要求程序员了解现有组件库中的相关组件的内部结构,所以应用相对较少些。
模式的结构
适配器模式包含以下主要角色:
- 目标(Target)角色:该角色定义把其他类转换为何种接口,也就是我们的期望接口。它可以是抽象类或接口。
- 源(Adaptee)角色:你想把谁转换为目标角色,这个“谁”就是源角色,他是已经存在的、运行良好的类或者对象,经过适配器角色的包装,他会成为一个新的角色。
- 适配器(Adapter)角色:是适配器模式的核心角色,其他两个角色都是已经存在的角色,而适配器角色是需要新建立的,他的职责很简单:把原角色转换为目标角色。
通用代码
//目标角色
public interface Target{
public void request();
}
//源角色
public class Adaptee{
public void doSomething(){
System.out.println("源角色的------doSomething");
}
}
//适配器角色
public class Adapter extends Adaptee implements Target{
public void request(){
super.doSomething();
}
}
//场景类
public class Client{
public static void main(String[] args){
Target target = new Adapter();
target.request();
}
}
3.2.2 适配器模式的优缺点
优点
- 客户端通过适配器可以透明地调用目标接口
- 复用了现存的类,程序员不需要修改原有代码而重用现有的适配者类
- 将目标类和适配者类解耦,解决了目标类和适配者类接口不一致的问题
缺点
- 对适配器来说,更换适配器的实现过程比较复杂
3.2.3 适配器模式的使用场景
适配器模式(Adapter)通常适用于以下场景:
- 以前开发的系统存在满足新系统功能需求的类,但其接口同新系统的接口不一致
- 使用第三方提供的组件,但组件接口定义和自己要求的接口定义不同
适配器模式是一个补偿模式,或者说是一个“补救”模式。通常用来解决接口不相容的问题。一般项目开始的时候用的偏少。大多是在项目的需求不断变化的时候,技术为了业务服务的,因此业务在变化的时候,对技术也提出了要求,这些时候可能就需要这样的补救模式诞生。
3.3 装饰(Decorator)模式
3.3.1 装饰模式的定义与结构
定义
指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式,它属于对象结构性模式。
结构与实现
通常情况下,扩展一个类的功能会使用继承方法来实现。但继承具有静态特征,耦合度高,并且随着扩展功能的增多,子类会很膨胀。如果使用组合关系来创建一个包装对象(即装饰对象)来包裹真实对象,并在保持真实对象的类结构不变的前期下,为其提供额外的功能,这就是装饰模式的目标。
结构
装饰模式主要包含以下角色:
- 抽象构建(Component)角色:是一个抽象类或者接口,定义最核心的对象,也就是最原始的对象。
- 具体构建(Concrete Component)角色:实现抽象结构,通过装饰角色为其添加一些职责。
- 抽象装饰(Decorator)角色:一般是一个抽象类,继承抽象构建,实现具体抽象方法,里面不一定有抽象的方法,在它的属性里一般都会有一个private变量指向Component抽象构件。
- 具体装饰(Decorator)角色:实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。
实现
//抽象构件
abstract class Component{
public abstract void operation();
}
//具体构件
public class ConcreteComponent extends Component{
@Override
public void operation(){
System.out.println("具体对象的操作");
}
}
//抽象装饰着
public abstract class Decorator extends Component{
private Component component = null;
//通过构造函数传递给被修饰者
public Decorator(Component component){
this.component = component;
}
//委托给被修饰者执行
@Override
public void operation(){
if(component != null){
this.component.operation();
}
}
}
//具体装饰者
public class ConcreteDdcoratorA extends Decorator{
//定义被修饰者
public ConcretrDecoratorA(Component component){
super(component);
}
//定义自己的修饰方法
private void method1(){
System.out.println("mehod1 修饰:")
}
@Override
public void operation(){
this.method1();
super.operation();
}
}
public class ConcreteDdcoratorB extends Decorator{
//定义被修饰者
public ConcretrDecoratorB(Component component){
super(component);
}
//定义自己的修饰方法
private void method2(){
System.out.println("mehod2 修饰:")
}
@Override
public void operation(){
this.method2();
super.operation();
}
}
//场景类
public class Client{
public static void main(String[] args){
Component component = new ConcreteComponent();
//第一次修饰
component = new ConcreteDecoratorA(component);
//第二次修饰
component = new ConcreteDecoratorB(component);
//修饰后运行
component.operation();
}
}
3.3.2 装饰者模式的优缺点
优点
- 采用装饰模式扩展对象的功能比采用继承方式更加灵活
- 可以设计出多个不同的具体装饰类,创造出多个不同行为的组合
缺点
- 装饰模式增加了许多子类,如果过度使用会使程序变得很复杂
3.3.3 装饰者模式的应用场景
通常在以下几种情况使用:
- 当需要给一个现有类添加附加职责,而又不能采用生成子类的方法进行扩充时。例如,该类被隐藏或者该类是终极类或者采用继承方式会产生大量的子类。
- 当需要通过对现有的一组基本功能进行排列组合而产生非常多的功能时,采用继承关系很难实现,而采用装饰模式却很好实现。
- 当对象的功能要求可以动态地添加,也可以再动态地撤销时。
- 需要为一批的兄弟类进行改装或者加装功能的时候,可以首选装饰模式
3.4 亨元(Flyweight)模式
3.3.1 定义与结构
定义
运用共享技术来有效地支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似类的开销,从而提高系统资源的利用率。
结构
亨元模式中存在以下两种状态:
- 内部状态,即不会随着环境的改变而改变的可共享部分
- 外部状态,指随环境改变而改变的不可以共享的部分。
亨元模式的实现要领就是区分应用中的这两种状态,并将外部状态外部化。
亨元模式的主要角色如下:
- 抽象亨元(Flyweight)角色:简单理解就是一个产品的抽象类,同时定义出对象的外部状态和内部状态的接口或者实现。
- 具体亨元(Concrete Flyweight)角色:实现抽象亨元角色中所规定的接口。
- 非亨元(Unsharable Flyweight)角色:是不可以共享的外部状态,它以参数的形式注入具体亨元的相关方法中。
- 亨元工厂(Flyweight Factory)角色:负责创建和管理亨元角色。当客户对象请求一个亨元对象时,亨元工厂检查系统中是否存在符合要求的亨元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的亨元对象。
实现
//抽象亨元角色
public abstract class Flyweight{
//内部状态
public String intrinsic;
//外部状态
protected final String extrinsic;
//要求亨元角色必须接受外部状态
public Flyweight(String extrinsic){
this.extrinsic = extrinsic;
}
//定义业务操作
public abstract void operate(int extrinsic);
public String getIntrinsic(){
return intrinsic;
}
public void setIntrinsic(String instrinsic){
this.intrinsic = intrinsic;
}
}
//具体亨元角色
public class ConcreteFlyweight extends Flyweight{
//接受外部状态
public ConcreteFlyweight(String extrinsic){
super(extrinsic);
}
@Override
public void operate(int extrinsic){
System.out.println("具体Flyweight:" + extrinsic);
}
}
//那些不需要共享的Flyweight子类
public class UnsharedConcreteFlyweight extends Flyweight{
public UnsharedConcreteFlyweight(String extrinsic){
super(extrinsic);
}
@Override
public void operate(int extrinsic){
System.out.println("不共享的具体Flyweight" + extrinsic);
}
}
//亨元工厂
public class FlyweightFactory{
//定义一个池容器
private static HashMap<String,Flyweight> pool = new HashMap<>();
//亨元工厂
public static Flyweight getFlyweight(String extrinsic){
Flyweight flyweight = null;
if(pool.containsKey(extrinsic)){//池中有该对象
flyweight = pool.get(extrinsic);
System.out.print("已有"+extrinsic+"直接从池中取---->");
} else {
//根据外部状态创建亨元对象
flyweight = new ConcreteFlyweight(extrinsic);
//放入池中
pool.put(extrinsic,flyweight);
System.out.print("创建"+extrinsic+"并从池中取出---->");
}
return flyweight;
}
}
3.4.2 亨元模式的优缺点:
优点:
- 大大减少应用程序创建的对象,相同对象只要保存一份,降低程序内存的占用,这降低了系统中对象的数量,从而降低了系统中细粒度给内存带来的压力。
缺点
- 为了使对象可以共享,需要将一些不能共享的状态外部化,这将增加程序的复杂性
- 读取亨元模式的外部状态会使得运行时间稍微变长
3.4.4 亨元模式的应用场景
- 系统中存在大量相同或相似的对象,这些对象耗费大量的内存资源
- 细粒度的对象都具备比较接近的外部状态,而且内部状态与环境无关,也就是说对象没有特定的身份
- 需要缓冲池的场景
4 设计模式之行为型模式
4.1 策略模式(Strateguy)
4.1.1 定义与结构
定义
该模式定义了一些列算法,并将每个算法封装起来,使他们可以相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。
结构
主要角色如下:
- 抽象策略(Startegy)类:定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,环境角色使用这个接口调用不同的算法,一般使用接口或抽象类实现。
- 具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现。
- 策略上下文角色(Strategy Context):策略上下文,负责和具体的策略实现交互,通常策略上下文对象会持有一个真正的策略实现对象,策略上下文还可以让具体的策略实现从其中获取相关数据,回调策略上下文对象的方法。
4.1.2 通用参考代码
//抽象策略类
public interface Strategy{
void strategyMethod();//策略方法
}
//具体策略类A
public class ConcreteStrategyA implements Strategy{
public void startegyMethod(){
System.out.println("具体策略A的策略方法被访问1")
}
}
//具体策略类B
public class ConcreteStrategyB implements Strategy{
public void startegyMethod(){
System.out.println("具体策略B的策略方法被访问1")
}
}
//环境类
class Context{
private Strategy strategy;
public Context(Strategy strategy){
this.strategy = strategy;
}
public void strategyMethod(){
strategy.startegyMethod();
}
}
public class Client{
public static void main(String[] args){
Strategy s = new ConcreteStrategyA();
Context c = new Context(s);
c.strategyMethod();
System.out.println("--------------");
s = new ConcreteStrategyB();
c = new Context(s);
c.strategyMethod();
}
}
4.1.3 策略模式优缺点
优点
- 多重条件语句不易维护,而是用策略模式可以避免使用多重条件语句。
- 策略模式提供了一系列的可供重用的算法族,恰当使用继承可以把算法族的公共代码转移到父类里面,从而避免重复的代码。
- 策略模式可以提供相同行为的不同实现,客户可以根据不同事件或空间要求选择不同的。
- 策略模式提供了对开闭原则的完美支持,可以在不修改源代码的情况下,灵活增加新算法。
- 策略模式把算法的使用放到环境类中,而算法的实现移到具体策略类中,实现了二者的分离。
缺点
- 客户端必须理解所有策略算法的区别,以便适时选择恰当的算法类。
- 策略模式造成很多的策略类。
4.1.4 使用场景
- 多个类只有在算法或行为上稍有不同的场景
- 算法需要自由切换的场景
- 需要屏蔽算法规则的场景
4.2 观察者(Observer)模式
4.2.1 观察者模式的定义与结构
定义
指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式,它是对象行为型模式的一种。
结构
- 被观察者(Subject):也叫抽象目标类,它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。
- 具体的被观察者(Concrete Subject):也叫具体目标类,它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象。
- 观察者(Observer):它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用。
- 具体的观察者(Concrete Observer):实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态。
通用参考源码
//被观察者
public abstract class Subject{
protected List<Observer> observers = new ArrayList<Observer>();
//增加观察者方法
public void add(Observer observer){
observers.add(observer);
}
//删除观察者方法
public void remove(Observer observer){
observers.remove(observer);
}
public abstract void notifyObserver();//通知观察者方法
}
//具体被观察者
public class ConcreteSubject extends Subject{
public void notifyObserver(){
System.out.println("具体目标发生改变。。。");
System.out.println("-------------------");
for(Object obs:observers){
((Obverser)obs).updateSelf();
}
}
}
//抽象观察者
public interface Observer{
void updateSelf();//反应
}
//具体观察者1
class ConcreteObserver1 implements Observer{
public void updateSelf(){
System.out.println("具体观察者1作出反应!")
}
}
//具体观察者2
class ConcreteObserver2 implements Observer{
public void updateSelf(){
System.out.println("具体观察者2作出反应!")
}
}
public class Client{
public static void main(String[] args){
Subject subject = new ConcreteSubject();
Observer obs1 = new ConcreteObserver1();
Observer obs2 = new ConcreteObserver2();
subject.add(obs1);
subject.add(obs2);
subject.notifyObserver();
}
}
4.2.2 观察者模式的优缺点
优点
- 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。
- 目标与观察者之间建立了一套触发机制。
缺点
- 目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用。
- 当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。
4.2.3 模式的应用场景
- 对象间存在一对多关系,一个对象的状态发生改变会影响其他对象。
- 当一个抽象模型有两个方面,其中一个方面依赖于另一方面时,可将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。
4.3 迭代器(Iterator)模式
4.3.1 模式的定义与结构
定义
提供一种方法访问一个容器对象中各个元素,而又不需要暴露该对象的内部细节。
(迭代器模式是一个没落的模式,模式现在一般都用在产品性质的开发中)
结构
迭代器模式主要包含以下角色:
- 抽象容器(Aggregate)角色:定义存储、添加、删除容器对象以及创建迭代器对象的接口。
- 具体容器(Concrete Aggregate)角色:实现抽象容器类,返回一个具体迭代器的实例。
- 抽象迭代器(Iterator)角色:定义访问和遍历容器元素的接口,,通常包含hasNext()、first()、next()等方法。
- 具体迭代器(ConcreteIterator)角色:实现抽象迭代器接口中所定义的方法,完成对容器对象的遍历,记录遍历的当前位置。
通用参考源码
//抽象容器
public interface Aggregate{
void add(Object obj);
boolean remove(Object obj);
Iterator iterator();
}
//具体容器
public class ConcreteAggregate implements Aggregate{
//也可以选择使用Vertor
private List<Object> list = new ArrayList<Object>();
public void add(Object obj){
list.add(obj);
}
public boolean remove(Object obj){
return list.remove(obj);
}
public Iterator iterator(){
return (new ConcreteIterator(list));
}
}
//抽象迭代器
public interface Iterator{
//遍历到下一个元素
Object next();
//是否已经遍历到最后一个
boolean hasNext();
//获取第一个元素
Object first();
//删除指定元素
boolean remove(Object obj);
}
//具体迭代器
class ConcreteIterator implements Iterator{
//也可以选择使用Vector
private List<Objcet> list = null;
//定义当前位置
private int index = 0;
public ConcreteIterator(List<Object> list){
this.list = list;
}
public boolean hasNext();{
if(index == list.size()){
return false;
}else{
return true;
}
}
public Object first(){
index = 0;
Object obj = list.get(index);
return obj;
}
public Object next(){
Object obj = null;
if(this.hasNext()){
obj = list.get(++index);
}
return obj;
}
public boolean remove(Object obj){
return this.list.remove(obj);
}
}
public class Client{
public static void main(String[] args){
Aggregate ag = new ConcreteAggregate();
ag.add("赵");
ag.add("送");
ag.add("哇");
System.out.println("遍历容器中的内容如下");
Iterator it = ag.iterator();
while(it.hasNext()){
Object obj = it.next();
System.out.println(ob.toString()+"\t");
}
Object ob = it.first();
System.out.println("\nFirst:"+ob.toString());
}
}
4.3.2 迭代器的优缺点
优点
- 访问一个聚合对象的内容而无需暴露它的内部表示。
- 遍历任务交由迭代器完成,这简化了聚合类。
- 它支持以不同方式遍历一个聚合,甚至可以自定义迭代器的子类以支持新的遍历。
- 增加新的聚合类和迭代器类都很方便,无需修改原有代码。
- 封装性很好,为遍历不同的聚合结构提供一个统一的接口。
缺点
增加了类的个数,这在一定程度上增加了系统的复杂性。
4.4 模板方法(Template Method)模式
4.4.1 模式的定义与结构
定义
定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。它是应用非常广泛的模式,使用了java的继承机制。
结构
(1) 抽象模板类(Abstract Class):负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。这些方法的定义如下:
- 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。
- 基本方法:是由子类实现的方法,并且在模板方法中被调用。一般包含以下三种类型的方法:
- 抽象方法:在模板方法类中声明,由具体子类实现。
- 具体方法:在模板方法类中已经实现,在具体子类中可以继承或重写它。
- 钩子方法:在模板方法类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。
(2) 具体模板类(Concrete Class):实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的一个组成步骤。
通用参考源码
//抽象模板类
public abstract class AbstractClass{
//基本方法:抽象方法1
protected abstract void doSomething();
//基本方法:抽象方法2
protected abstract void doAnything();
//基本方法:具体方法
protected void specificMethod(){
//具体方法的业务逻辑
}
//模板方法
public void templateMethod(){
//调用基本方法,完成相关逻辑
this.specificMethod();
this.doSomething();
this.doAnything();
}
}
//具体模板类
public class ConcreteClass1 extends AbstractClass{
//实现基本方法
protected abstract void doSomething(){
//处理业务逻辑
}
protected abstract void doAnything(){
//处理业务逻辑
}
}
public class ConcreteClass2 extends AbstractClass{
//实现基本方法
protected abstract void doSomething(){
//处理业务逻辑
}
protected abstract void doAnything(){
//处理业务逻辑
}
}
//场景类
public class Client{
public static void main(String[] args){
AbstractClass h1 = new ConcreteClass1();
AbstractClass h2 = new ConcreteClass2();
//调用模板方法
h1.templateMethod();
h2.templateMethod();
}
}
4.4.2 模板方法模式的优缺点
优点
- 它封装了不变部分,扩展可变部分。它把认为是不变部分的算法封装到父类中实现,而把可变部分算法由子类继承实现,便于子类继续扩展。
- 它在父类中提取了公共的部分代码,便于代码复用。
- 部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则。
缺点
- 对每个不同的实现都需要定义一个子类,这导致类的个数增加,系统更加庞大,设计也更加抽象。
- 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。
4.4.3 模板方法模式的扩展
修改后的通用参考源码
//抽象模板类
public abstract class AbstractClass {
//具体方法:钩子方法1
protected void hookMethod1(){}
//具体方法:钩子方法2
protected boolean hookMethod2(){
return true;
}
//基本方法:抽象方法1
protected abstract void doSomething();
//基本方法:抽象方法2
protected abstract void doAnything();
//基本方法:具体方法
protected void specificMethod(){
//具体方法的业务逻辑
}
//模板方法
public void templateMehtod() {
//调用基本方法,完成相关逻辑
this.doSomething();
hookMethod1();
if(hookMethod2()){
this.specificMethod();
}
this.doAnything();
}
}
//具体模板类:含有钩子方法
public class ConcreteClass1 extends AbstractClass{
//实现基本方法
protected abstract void doSomething(){
//处理业务逻辑
}
protected abstract void doAnything(){
//处理业务逻辑
}
public void HookMethod1(){
System.out.println("钩子方法1被重写...");
}
public boolean HookMethod2(){
return false;
}
}
public class ConcreteClass2 extends AbstractClass{
//实现基本方法
protected abstract void doSomething(){
//处理业务逻辑
}protected abstract void doAnything(){
//处理业务逻辑
}
}
//场景类
public class Client {
public static void main(String[] args) {
AbstractClass h1=new ConcreteClass1();
AbstractClass h2=new ConcreteClass2();
//调用模板方法
h1.templateMehtod();
h2.templateMehtod();
}
}
4.4.4 模板方法的应用场景
- 多个子类有共有的方法,并且基本逻辑相同的时候。
- 重要复杂的算法,可以把核心算法设计为模板方法,周边的相关细节功能可以由各个子类实现。
- 重构的时候,模板方法模式是一个经常使用的模式,把相同的代码抽取到父类中,然后通过钩子方法约束其行为。