一、什么是设计模式?
定义
设计模式是对某情景下,针对某种问题的某种解决方案,包括三个基本元素–场景、问题、解决方案。
是对设计原则的具体化。用江湖话说就是武林秘籍,总结出来的一些固定套路,可以帮助有根基的程序员迅速打通任督二脉,从此做什么都特别快。
设计模式其实并不是什么新的知识或者说是一个比较难以去学习的东西,他只是在面向对象编程的思想基础上,运用面向对象编程的基本思想去合理的组织代码结构,通过继承、多态、封装等特性来对程序进行重构,以达到各类预期的效果,如可扩展性,易维护性,易复用等特点,减少在软件开发过程中因为需求的不断变化而导致程序的更改愈加困难。
六大原则
单一原则(Single Responsibility Principle)
一个类或者一个方法只负责一项职责,尽量做到类的只有一个行为原因引起变化;业务对象(BO business object)、业务逻辑(BL business logic)拆分;
里氏替换原则(LSP liskov substitution principle)
子类可以扩展父类的功能,但不能改变原有父类的功能;(本质其实就是多态)
(目的:增强程序的健壮性)实际项目中,每个子类对应不同的业务含义,使父类作为参数,传递不同的子类完成不同的业务逻辑。
依赖倒置原则(dependence inversion principle)
面向接口编程;(通过接口作为参数实现应用场景)
抽象就是接口或者抽象类,细节就是实现类
含义:
上层模块不应该依赖下层模块,两者应依赖其抽象;
抽象不应该依赖细节,细节应该依赖抽象;
通俗点就是说变量或者传参数,尽量使用抽象类,或者****接口;
【接口负责定义public属性和方法,并且申明与其他对象依赖关系,抽象类负责公共构造部分的实现,实现类准确的实现业务逻辑】
接口****隔离原则(interface segregation principle)
建立单一接口;(扩展为类也是一种接口,一切皆接口)
定义:
a.客户端不应该依赖它不需要的接口;
b.类之间依赖关系应该建立在最小的接口上;
简单理解:复杂的接口,根据业务拆分成多个简单接口;(对于有些业务的拆分多看看适配器的应用)
【接口的设计粒度越小,系统越灵活,但是灵活的同时结构复杂性提高,开发难度也会变大,维护性降低】
迪米特原则(law of demeter LOD)
最少知道原则,尽量降低类与类之间的耦合;
一个对象应该对其他对象有最少的了解
开闭原则(open closed principle)
用抽象构建架构,用实现扩展原则
二、有哪些设计模式?
创建型模式
对类的实例化过程的抽象。一些系统在创建对象时,需要动态地决定怎样创建对象,创建哪些对象,以及如何组合和表示这些对象。创建模式描述了怎样构造和封装这些动态的决定。包含类的创建模式和对象的创建模式。
包含5个:建造者、工厂模式(抽象工厂和工厂方法)、单例、原型
结构型模式
描述如何将类或对象结合在一起形成更大的结构。分为类的结构模式和对象的结构模式。类的结构模式使用继承把类,接口等组合在一起,以形成更大的结构。类的结构模式是静态的。对象的结构模式描述怎样把各种不同类型的对象组合在一起,以实现新的功能的方法。对象的结构模式是动态的。
包含7个:外观、桥接、装饰、适配器、代理、组合、享元
行为型模式
对在不同的对象之间划分责任和算法的抽象化。不仅仅是关于类和对象的,并是关于他们之间的相互作用。类的行为模式使用继承关系在几个类之间分配行为。对象的行为模式则使用对象的聚合来分配行为。
包含11个:访问者、状态、迭代、命令、责任链、备忘录、中介、策略、模板、观察者、解释器、
频率
|
所属类型
|
模式名称
|
模式
|
简单定义
|
5
|
创建型
|
Singleton
|
单件
|
保证一个类只有一个实例,并提供一个访问它的全局访问点。
|
5
|
结构型
|
Composite
|
组合模式
|
将对象组合成树形结构以表示部分整体的关系,Composite使得用户对单个对象和组合对象的使用具有一致性。
|
5
|
结构型
|
FAÇADE
|
外观
|
为子系统中的一组接口提供一致的界面,facade提供了一高层接口,这个接口使得子系统更容易使用。
|
5
|
结构型
|
Proxy
|
代理
|
为其他对象提供一种代理以控制对这个对象的访问
|
5
|
行为型
|
Iterator
|
迭代器
|
提供一个方法顺序访问一个聚合对象的各个元素,而又不需要暴露该对象的内部表示。
|
5
|
行为型
|
Observer
|
观察者
|
定义对象间一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知自动更新。
|
5
|
行为型
|
Template Method
|
模板方法
|
定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,Template Method使得子类可以不改变一个算法的结构即可以重定义该算法得某些特定步骤。
|
4
|
创建型
|
Abstract Factory
|
抽象工厂
|
提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们的具体类。
|
4
|
创建型
|
Factory Method
|
工厂方法
|
定义一个用于创建对象的接口,让子类决定实例化哪一个类,Factory Method使一个类的实例化延迟到了子类。
|
4
|
结构型
|
Adapter
|
适配器
|
将一类的接口转换成客户希望的另外一个接口,Adapter模式使得原本由于接口不兼容而不能一起工作那些类可以一起工作。
|
4
|
结构型
|
Decorator
|
装饰
|
动态地给一个对象增加一些额外的职责,就增加的功能来说,Decorator模式相比生成子类更加灵活。
|
4
|
行为型
|
Command
|
命令
|
将一个请求封装为一个对象,从而使你可以用不同的请求对客户进行参数化,对请求排队和记录请求日志,以及支持可撤销的操作。
|
4
|
行为型
|
State
|
状态
|
允许对象在其内部状态改变时改变他的行为。对象看起来似乎改变了他的类。
|
4
|
行为型
|
Strategy
|
策略模式
|
定义一系列的算法,把他们一个个封装起来,并使他们可以互相替换,本模式使得算法可以独立于使用它们的客户。
|
3
|
创建型
|
Builder
|
生成器
|
将一个复杂对象的构建与他的表示相分离,使得同样的构建过程可以创建不同的表示。
|
3
|
结构型
|
Bridge
|
桥接
|
将抽象部分与它的实现部分相分离,使他们可以独立的变化。
|
3
|
行为型
|
China of Responsibility
|
职责链
|
使多个对象都有机会处理请求,从而避免请求的送发者和接收者之间的耦合关系
|
2
|
创建型
|
Prototype
|
原型
|
用原型实例指定创建对象的种类,并且通过拷贝这些原型来创建新的对象。
|
2
|
结构型
|
Flyweight
|
享元
|
享元模式以共享的方式高效的支持大量的细粒度对象。享元模式能做到共享的关键是区分内蕴状态和外蕴状态。内蕴状态存储在享元内部,不会随环境的改变而有所不同。外蕴状态是随环境的改变而改变的。
|
2
|
行为型
|
Mediator
|
中介者
|
用一个中介对象封装一些列的对象交互。
|
2
|
行为型
|
Visitor
|
访问者模式
|
表示一个作用于某对象结构中的各元素的操作,它使你可以在不改变各元素类的前提下定义作用于这个元素的新操作。
|
1
|
行为型
|
Interpreter
|
解释器
|
给定一个语言,定义他的文法的一个表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
|
1
|
行为型
|
Memento
|
备忘录
|
在不破坏对象的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。
|
三、设计模式如何使用?
如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。
优点:不会频繁地创建和销毁对象,浪费系统资源。
使用场景
IO 、数据库连接、Redis 连接等
示例
class SingletonLazy {
private static SingletonLazy instance;
public static SingletonLazy getInstance() {
if (instance == null) {
instance = new SingletonLazy();
}
return instance;
}
}
组合模式,又叫 “部分整体” 模式,将对象组合成树形结构,以表示 “部分-整体” 的层次结构。通过对象的多态性表现,使得用户对单个对象和组合对象的使用具有一致性
使用场景
- 模拟文件扫描功能
示例
import java.util.ArrayList;
import java.util.List;
abstract class Component {
protected String name;
public Component(String name) {
this.name = name;
}
public abstract void Add(Component c);
public abstract void Remove(Component c);
public abstract void GetChild(int depth);
}
class Leaf extends Component {
public Leaf(String name) {
super(name);
}
@Override
public void Add(Component c) {
System.out.println("Can not add to a leaf");
}
@Override
public void Remove(Component c) {
System.out.println("Can not remove from a leaf");
}
@Override
public void GetChild(int depth) {
String temp = " ";
for (int i = 0; i < depth; i++) {
temp += "-";
System.out.println(temp + name);
}
}
}
class Composite extends Component {
private List<Component> children = new ArrayList<>();
public Composite(String name) {
super(name);
}
@Override
public void Add(Component c) {
children.add(c);
}
@Override
public void Remove(Component c) {
children.remove(c);
}
@Override
public void GetChild(int depth) {
for (Component c : children) {
c.GetChild(depth);
}
}
}
public class Main {
public static void main(String args[]) {
Composite root = new Composite("root");
root.Add(new Leaf("Leaf A"));
root.Add(new Leaf("Leaf B"));
Composite compX = new Composite("Composite X");
compX.Add(new Leaf("Leaf XA"));
compX.Add(new Leaf("Leaf XB"));
root.Add(compX);
Composite compXY = new Composite("Composite XY");
compXY.Add(new Leaf("Leaf XYA"));
compXY.Add(new Leaf("Leaf XYB"));
compX.Add(compXY);
root.GetChild(3);
}
}
又叫作门面模式,是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。该模式对外有一个统一接口,外部应用程序不用关心内部子系统的具体细节,这样会大大降低应用程序的复杂度,提高了程序的可维护性。
使用场景
如办房产证或注册一家公司,有时要同多个部门联系,这时要是有一个综合部门能解决一切手续问题就好了
示例
package facade;
public class FacadePattern {
public static void main(String[] args) {
Facade f = new Facade();
f.method();
}
}
//外观角色
class Facade {
private SubSystem01 obj1 = new SubSystem01();
private SubSystem02 obj2 = new SubSystem02();
private SubSystem03 obj3 = new SubSystem03();
public void method() {
obj1.method1();
obj2.method2();
obj3.method3();
}
}
//子系统角色
class SubSystem01 {
public void method1() {
System.out.println("子系统01的method1()被调用!");
}
}
//子系统角色
class SubSystem02 {
public void method2() {
System.out.println("子系统02的method2()被调用!");
}
}
//子系统角色
class SubSystem03 {
public void method3() {
System.out.println("子系统03的method3()被调用!");
}
}
代理模式是给某一个对象提供一个代理,并由代理对象控制对原对象的引用。
使用场景
优点:
-
代理模式能够协调调用者和被调用者,在一定程度上降低了系统的耦合度;
-
可以灵活地隐藏被代理对象的部分功能和服务,也增加额外的功能和服务。
缺点:
-
由于使用了代理模式,因此程序的性能没有直接调用性能高;
-
使用代理模式提高了代码的复杂度。
示例
举一个生活中的例子:比如买飞机票,由于离飞机场太远,直接去飞机场买票不太现实,这个时候我们就可以上携程 App 上购买飞机票,这个时候携程 App 就相当于是飞机票的代理商。
/** 定义售票接口 */
interface IAirTicket {
void buy();
}
/** 定义飞机场售票 */
class AirTicket implements IAirTicket {
@Override
public void buy() {
System.out.println("买票");
}
}
/** 代理售票平台 */
class ProxyAirTicket implements IAirTicket {
private AirTicket airTicket;
public ProxyAirTicket() {
airTicket = new AirTicket();
}
@Override
public void buy() {
airTicket.buy();
}
}
/** 代理模式调用 */
public class ProxyTest {
public static void main(String[] args) {
IAirTicket airTicket = new ProxyAirTicket();
airTicket.buy();
}
}
关系图
迭代器模式提供一种方法顺序访问一个聚合对象中各个元素,而又不暴露该对象的内部表示。
使用场景
当你需要访问一个聚集对象,而且不管这些对象是什么都需要遍 历的时候,就应该考虑用迭代器模式。
观察者模式又被称作发布/订阅模式,定义了对象间一对多依赖,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。
优点:
-
观察者模式可以实现表示层和数据逻辑层的分离,并定义了稳定的消息更新传递机制,抽象了更新接口,使得可以有各种各样不同的表示层作为具体观察者角色;
-
观察者模式在观察目标和观察者之间建立一个抽象的耦合;
-
观察者模式支持广播通信;
-
观察者模式符合开闭原则(对拓展开放,对修改关闭)的要求。
缺点:
-
如果一个观察目标对象有很多直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间;
-
如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃;
-
观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
使用场景
模板方法模式定义一个操作中的算法的骨架,将一些步骤延迟到子类中,模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些步骤。
优点:
-
提高代码复用性:将相同部分的代码放在抽象的父类中,而将不同的代码放入不同的子类中;
-
实现了反向控制:通过一个父类调用其子类的操作,通过对子类的具体实现扩展不同的行为,实现了反向控制并且符合开闭原则。
使用场景
对于一些功能,在不同的对象身上展示不同的作用,但是功能的框架是一样的。
以给冰箱中放水果为例,比如,我要放一个香蕉:开冰箱门 → 放香蕉 → 关冰箱门;如果我再要放一个苹果:开冰箱门 → 放苹果 → 关冰箱门。可以看出它们之间的行为模式都是一样的,只是存放的水果品类不同而已,这个时候就非常适用模板方法模式来解决这个问题
示例
/\* \* 添加模板方法 \*/
abstract class Refrigerator {
public void open() {
System.out.println("开冰箱门");
}
public abstract void put();
public void close() {
System.out.println("关冰箱门");
}
}
class Banana extends Refrigerator {
@Override
public void put() {
System.out.println("放香蕉");
}
}
class Apple extends Refrigerator {
@Override
public void put() {
System.out.println("放苹果");
}
}
/\* \* 调用模板方法 \*/
public class TemplateTest {
public static void main(String[] args) {
Refrigerator refrigerator = new Banana();
refrigerator.open();
refrigerator.put();
refrigerator.close();
}
}
工厂模式主要是为创建对象提供了接口。
类别
简单工厂
简单工厂模式又叫静态工厂方法模式,就是建立一个工厂类,对实现了同一接口的一些类进行实例的创建。比如,一台咖啡机就可以理解为一个工厂模式,你只需要按下想喝的咖啡品类的按钮(摩卡或拿铁),它就会给你生产一杯相应的咖啡,你不需要管它内部的具体实现,只要告诉它你的需求即可。
优点:
-
工厂类含有必要的判断逻辑,可以决定在什么时候创建哪一个产品类的实例,客户端可以免除直接创建产品对象的责任,而仅仅“消费”产品;简单工厂模式通过这种做法实现了对责任的分割,它提供了专门的工厂类用于创建对象;
-
客户端无须知道所创建的具体产品类的类名,只需要知道具体产品类所对应的参数即可,对于一些复杂的类名,通过简单工厂模式可以减少使用者的记忆量;
-
通过引入配置文件,可以在不修改任何客户端代码的情况下更换和增加新的具体产品类,在一定程度上提高了系统的灵活性。
缺点:
-
不易拓展,一旦添加新的产品类型,就不得不修改工厂的创建逻辑;
-
产品类型较多时,工厂的创建逻辑可能过于复杂,一旦出错可能造成所有产品的创建失败,不利于系统的维护。
抽象工厂
抽象工厂模式是在简单工厂的基础上将未来可能需要修改的代码抽象出来,通过继承的方式让子类去做决定。
比如,以上面的咖啡工厂为例,某天我的口味突然变了,不想喝咖啡了想喝啤酒,这个时候如果直接修改简单工厂里面的代码,这种做法不但不够优雅,也不符合软件设计的“开闭原则”,因为每次新增品类都要修改原来的代码。这个时候就可以使用抽象工厂类了,抽象工厂里只声明方法,具体的实现交给子类(子工厂)去实现,这个时候再有新增品类的需求,只需要新创建代码即可。
工厂方法
使用场景
定义了算法族,分别封装起来,让它们之间可以互相替换。此模式让算法的变化独立于使用算法的客户。
使用场景
- 一件事情,有很多方案可以实现。
- 我可以在任何时候,决定采用哪一种实现。
- 未来可能增加更多的方案。
- 策略模式让方案的变化不会影响到使用方案的客户。
如:写数据到数据库、文件。只关注写的动作,具体写到哪里有多种方案。
UML图
示例
以生活中的例子来说,比如我们要出去旅游,选择性很多,可以选择骑车、开车、坐飞机、坐火车等,就可以使用策略模式,把每种出行作为一种策略封装起来,后面增加了新的交通方式了,如超级高铁、火箭等,就可以不需要改动原有的类,新增交通方式即可,这样也符合软件开发的开闭原则。
/** 声明旅行 */
interface ITrip {
void going();
}
class Bike implements ITrip {
@Override
public void going() {
System.out.println("骑自行车");
}
}
class Drive implements ITrip {
@Override
public void going() {
System.out.println("开车");
}
}
/** 定义出行类 */
class Trip {
private ITrip trip;
public Trip(ITrip trip) {
this.trip = trip;
}
public void doTrip() {
this.trip.going();
}
}
/** 执行方法 */
public class StrategyTest {
public static void main(String[] args) {
Trip trip = new Trip(new Bike());
trip.doTrip();
}
}
适配器模式是将一个类的接口变成客户端所期望的另一种接口,从而使原本因接口不匹配而无法一起工作的两个类能够在一起工作。
优点:
-
可以让两个没有关联的类一起运行,起着中间转换的作用;
-
灵活性好,不会破坏原有的系统。
缺点:过多地使用适配器,容易使代码结构混乱,如明明看到调用的是 A 接口,内部调用的却是 B 接口的实现。
示例
以生活中的例子来说,比如有一个充电器是 MicroUSB 接口,而手机充电口却是 TypeC 的,这个时候就需要一个把 MicroUSB 转换成 TypeC 的适配器
/** 传统的充电线 MicroUSB */
interface MicroUSB {
void charger();
}
/** TypeC 充电口 */
interface ITypeC {
void charger();
}
class TypeC implements ITypeC {
@Override
public void charger() {
System.out.println("TypeC 充电");
}
}
/** 适配器 */
class AdapterMicroUSB implements MicroUSB {
private TypeC typeC;
public AdapterMicroUSB(TypeC typeC) {
this.typeC = typeC;
}
@Override
public void charger() {
typeC.charger();
}
}
/** 测试调用 */
public class AdapterTest {
public static void main(String[] args) {
TypeC typeC = new TypeC();
MicroUSB microUSB = new AdapterMicroUSB(typeC);
microUSB.charger();
}
}
四、结语
学习了设计模式以及一些原则之后,切忌不可在自己的实际运用过程中,强行的按照设计模式原则的方式进行硬性模仿,世上没有绝对的东西,这些设计模式有的可能会互补,也有互相矛盾之处,都是前人在长期的软件开发过程中总结出来的一些比较好的思想,大家要根据自己的实际场景进行有所取舍的运用,主要是让你从思想上对面向对象程序编程有一个高度的认识。
五、深造
秘籍
《大话设计模式》
《设计模式》
《设计模式解析》
《重构与模式》
UML关系
从左到右分别是依赖、关联、聚合、组合、实现、继承
练习