设计模式的概念
:Design Pattern,DP
(Software Design Pattern),也被称为设计模式,它描述了软件设计过程中的一些重复问题,以及问题的解决方案。也就是说,它是解决特定问题的一系列常规,是对前辈代码设计经验的总结,具有一定的普遍性,可以重复使用。其目的是提高代码的可重用性、可读性和可靠性。
1.设计模式的六个设计原则
1.1 开闭原则:OCP
1.1.1 定义
- 。
简单来说,就是:。
软件实体包括以下几个部分:
- 项目中划分的模块
- 类与接口
- 方法
软件产品在其生命周期周期内发生变化。开闭原则是限制软件实体未来事件的当前开发设计的原则。
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.
- 第二种:Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.
里氏替换原则是继承和再利用的基石,它为良好的继承定义了一个规范,中包含了四层含义:
-
- 在以前的项目中,接口或抽象类经常被定义,然后编码实现。调用类直接传输到接口或抽象类。事实上,这就是里氏替换原则。
-
这里的字类不是重写父类的方法,而是重载父类的方法。由于子类和父类方法的输入参数不同。 子类方法的参数大于父类方法的参数,因此当参数输入为父类参数类型时,只执行父类方法,不执行父类重载方法。
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 依靠倒置原则的实现方法/h3> 依赖倒置原则的目的是通过要面向接口的编程来降低类间的耦合性,所以我们在实际编程中只要遵循以下几点,就能在项目中满足这个规则。
- 每个类尽量提供接口或抽象类,或者两者都具备
- 变量的声明类型尽量是接口或者是抽象类
- 任何类都不应该从具体类派生
- 尽量不要覆写基类的方法
- 使用继承时结合里氏替换原则
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 抽象工厂模式的定义与特点
的定义:为创建一组相关或者相互依赖的对象提供一个接口,而无需指定他们的具体类。
抽象工厂模式是工厂方法模式的升级版本,在有多个业务品种、业务分类时,通过抽象工厂模式产生需要的对象一种非常好的解决方案。工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品。
使用抽象工厂模式需要满足以下条件:
- 系统中有多个产品族,每个具体工厂创建同一族但属于不同等级结构的产品。
- 系统一次只可能消费其中某一族产品,即同族的产品一起使用。
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、代理模式的主要角色
- :抽象主题类可以是接口或抽象类,是一个普通的业务类型定义,声明真实主题和代理对象实现的业务方法,无特殊要求。
- :真实主题角色类也叫做被委托角色、被代理角色,实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象,是业务逻辑的具体执行者。
- :也叫委托类、代理类。它负责对真实角色的应用,把所有抽象类定义的方法限制委托给真实主题角色实现,并且在真实主题角色处理完毕前后做预处理和善后的工作。提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。
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()