提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
这章内容看似很少实则最多,哭了
一、面向可复用性和可维护性的设计模式
主要有三个大方面的分类:
1、Creational patterns 创建型模式:
工厂方法模式Factory Method pattern
2、Structural patterns 结构型模式
适配器模式Adapter;装饰器模式Decorator
3、Behavioral patterns 行为类模式
策略模式Strategy;模板模式Template Method;迭代器模式Iterator;访问者模式Visitor
1、工厂方法模式Factory Method pattern
谈到工厂方法模式,就先说应用场景。一个接口可以有多个实现类(具体产品),而使用的时候会面临选择哪一个进行使用的问题。另外,我们不想让客户端知道是哪一个类是接口的实现类,以达到信息隐藏,这个时候就引入了工厂方法。
工厂方法有三种:简单工厂、工厂方法、抽象工厂
简单工厂
只创建一个工厂类,通过输入参数来决定创建哪一个产品的对象。
public class ShapeFactory {
//使用 getShape 方法获取形状类型的对象
public Shape getShape(String shapeType){
if(shapeType == null){
return null;
}
if(shapeType.equalsIgnoreCase("CIRCLE")){
return new Circle();
} else if(shapeType.equalsIgnoreCase("RECTANGLE")){
return new Rectangle();
} else if(shapeType.equalsIgnoreCase("SQUARE")){
return new Square();
}
return null;
}
}
缺点:要么不扩展,扩展的话就破坏了OCP原则
工厂方法
定义一个工厂接口,其实现类是多个工厂类,分别对应不同具体产品
工厂方法包含以下几个核心角色:
抽象产品:
定义了产品的共同接口或抽象类。它可以是具体产品类的父类或接口,规定了产品对象的共同方法。
具体产品:
实现了抽象产品接口,定义了具体产品的特定行为和属性。
抽象工厂:
声明了创建产品的抽象方法,可以是接口或抽象类。它可以有多个方法用于创建不同类型的产品。
具体工厂:
实现了抽象工厂接口,负责实际创建具体产品的对象。在写具体工厂类的时候,可以是非静态工厂方法(使用时需要创建相应工厂类对象)和静态工厂方法(不需要创建工厂类对象,但是不能继承扩展)两种方式
工厂方法改进了简单工厂,满足OCP原则对扩展开放对修改封闭的特性
抽象工厂
和前面两者最大的不同在于它实现了不同类型产品的组合(前面是同一类型的不同产品)。
这里不再详细赘述,找了些不错的文章:
链接1: link
链接2: link
链接3: link
2、适配器模式Adapter
将某个类/接口转换为client期望的其他形式。本质上是在客户端期望接口和现有不兼容接口之间搭建桥梁。有类适配器、对象适配器、接口适配器三种。
类适配器
适配器类继承extends不兼容的具体类,同时实现implements客户端期望接口。然后在实现客户端期望接口的方法中调用从不兼容具体类继承过来的方法,实现改造。
//被适配的类
public class Voltage220V {
public int output220V() {
int src = 220;
System.out.println("电压=" + src);
return src;
}
}
//适配接口
public interface IVoltage5V {
public int output5V();
}
//适配器类
public class VoltageAdapter extends Voltage220V implements IVoltage5V {
@Override
public int output5V() {
// TODO Auto-generated method stub
//获取220V电压
int srcV = output220V();
int dstV = srcV / 44 ; //转成5v
return dstV;
}
}
//调用
public class Phone {
public void charging(IVoltage5V iVoltage5V) {
if(iVoltage5V.output5V() == 5) {
System.out.println("电压是5v,可以充电");
} else if (iVoltage5V.output5V() > 5) {
System.out.println("电压大于5v,不可充电");
}
}
}
//测试
public class Client {
public static void main(String[] args) {
System.out.println("===类适配器测试===");
Phone phone = new Phone();
phone.charging(new VoltageAdapter());
}
}
对象适配器
不是以继承而是以delegation委派的方式来调用不兼容类的方法
//被适配的类(不变)
public class Voltage220V {
public int output220V() {
int src = 220;
System.out.println("电压=" + src);
return src;
}
}
//适配接口(不变)
public interface IVoltage5V {
public int output5V();
}
//适配器类
public class VoltageAdapter implements IVoltage5V {
private Voltage220V voltage220V;
//构造器
public VoltageAdapter(Voltage220V voltage220v) {
this.voltage220V = voltage220v;
}
@Override
public int output5V() {
int dst = 0;
if(null != voltage220V) {
int src = voltage220V.output220V();//获取220v电压
dst = src / 44;
}
return dst;
}
}
//调用(不变)
public class Phone {
public void charging(IVoltage5V iVoltage5V) {
if(iVoltage5V.output5V() == 5) {
System.out.println("电压是5v,可以充电");
} else if (iVoltage5V.output5V() > 5) {
System.out.println("电压大于5v,不可充电");
}
}
}
//测试
public class Client {
public static void main(String[] args) {
System.out.println("===对象适配器===");
Phone phone = new Phone();
VoltageAdapter VoltageAdapter = new VoltageAdapter(new Voltage220V());
phone.charging(VoltageAdapter);
}
}
接口适配器
事实上,客户端期望接口并不是每个方法都与现有接口方法不兼容,也就是说很多方法不需要适配器去重写。这个时候可以定义一个抽象适配器类(加absract是为了不让其实例化),实现客户端需要接口,对于接口中方法直接写空实现。最后再写一个抽象适配器类的子类来重写改造需要适配的方法(需要哪个改造哪个)。重写方法的时候,可以不直接定义子类,而是在应用时通过匿名内部类的方式来重写方法兼创建子类对象。(对于调用不兼容类方法部分可在抽象适配器类中同前面选择继承extends或者delegation委派中一种方式)
//被适配的类(不变)
public class Voltage220V {
public int output220V() {
int src = 220;
System.out.println("电压=" + src);
return src;
}
}
//适配接口
public interface IVoltage5V {
public int output5V();
public void m2(); //接口里冗余不重要的方法
public String m3();
}
//抽象适配器
public abstract class AbsAdapter extends Voltage220V implements IVoltage5V{
@Override //以空方法实现接口所有方法
public int output5V() {
return 0;
}
@Override
public void m2() {
}
@Override
public String m3() {
return null;
}
}
//调用(不变)
public class Phone {
public void charging(IVoltage5V iVoltage5V) {
if(iVoltage5V.output5V() == 5) {
System.out.println("电压是5v,可以充电");
} else if (iVoltage5V.output5V() > 5) {
System.out.println("电压大于5v,不可充电");
}
}
}
//测试
public class Client {
public static void main(String[] args) {
System.out.println("===接口适配器===");
AbsAdapter absAdapter = new AbsAdapter() { //匿名内部类的形式
@Override //按需要重写接口方法
public int output5V() {
System.out.println("使用了output5V的方法");
int srcV = output220V();
int dstV = srcV / 44 ; //转成5v
return dstV;
}
};
Phone phone = new Phone();
phone.charging(absAdapter);
}
}
这里参考了一些别人的blog:link
3、装饰器模式Decorator
装饰器模式主要有四个核心角色:
1、Component抽象接口
2、ConcreteComponent接口的具体实现类
3、Decorator抽象装饰器
实现抽象接口,委派抽象接口(接口是其聚合关系),加abstract定义为抽象不被实例化
4、ConcreteDecorator具体装饰器
继承抽象装饰器,实现新特性
本该装饰器模式是较难理解的,但是经过Lab3,装饰器模式反倒变成最深刻的了,这里就不再详细赘述了,关系图阐述得非常清楚。
4、策略模式Strategy
有多种不同的算法来实现同一个任务,但需要client根据需要动态切换算法,而不是写死在代码里。为不同的实现算法构造抽象接口,利用delegation,运行时动态传入client倾向的算法。
看第一眼时,我还以为我看到了工厂方法,但是二者其实存在区别。工厂方法侧重于创建对象,而策略模式侧重于使用方法。因为接口存在不同实现的问题,所以都要有选择对象的过程;不同的是,工厂方法主要是创建并返回一个实现类的对象,而策略模式的对象是在客户端选择并创建的,策略模式本身只是为了使用该对象的方法。
这个过程主要有三个角色:
1、接口Strategy(抽象策略类)
2、具体策略类Concrete Strategy(具体实现类)
3、环境类Context(上下文类)
环境类以Strategy为参数实现委派,从而调用某一个策略对象的方法
5、模板模式Template Method
有些时候,做事情的步骤一样,但具体方法不同,这个时候就可以使用模板方法。主要使用继承和重写实现模板模式。
对比策略模式,两者都是实现方法不同,但是策略模式用的是委派机制,而模板模式用的是继承+重写模式。
在顶层设置一个抽象类,抽象类里有一个模板方法和若干个未实现的步骤方法。模板方法会调用步骤方法,模板方法自身设置为final(不应该被重写),步骤方法是protected(外界只调用模板方法),而后在子类中重写步骤方法即可。
不妨思考一个问题,为什么顶层用的是抽象类而不是接口呢?
其一,抽象类有属性(成员变量),而接口不能,此为一胜;
其二,抽象类可以带多个有实现的方法;而对于接口而言,尽管JDK8以后支持默认方法(有实现方法体),但是只能带一个,而如果用的是静态方法,又不能被重写,性能较差,此为二胜。
6、迭代器模式Iterator
主要有两个部分:
1、让自定义集合类实现接口Iterable,重写iterator()方法
2、为自定义集合类创建接口Iterator的新实现类,重写hasNext(),next(),remove()方法,让前面的iterator()方法返回的是这个新的实现类
在这个过程中,Iterator新实现类也可以放在集合类里作为内部类
主要有四个核心成员:
1、抽象迭代器(Iterator):定义了访问和遍历元素的接口,通常包含hasNext()、next()、remove()等方法,如Iterator接口。
2、具体迭代器(ConcreteIterator):抽象迭代器接口的实现类,负责遍历聚合对象并跟踪当前位置,即针对具体集合类的特定迭代器。
3、抽象聚合(Aggregate):含有能返回迭代器对象的方法的接口,如Iterable(含有iterator()方法)
4、具体聚合(ConcreteAggregate):实现了抽象聚合类,并返回具体迭代器的实例。就是我们的自定义集合类,实现Iterable接口,重写iterator()方法
7、访问者模式Visitor
访问者模式(Visitor Pattern)是一种将数据操作与数据结构分离的设计模式。它将一个数据结构(例如一个对象结构)中的元素的操作分离出来封装在一个独立的访问者类中,然后在不改变数据结构的前提下定义作用于这些元素的新操作。
主要有四个角色:
1、访问者接口:提供各种元素的visit方法(可重载),参数是各种元素的类型
2、具体访问者:访问者接口的实现类(一般是一个),重写各visit方法,对数据进行操作
3、元素接口:各元素的父类,提供accept()方法以参数形式接受外部的访问者对象(未实现)
4、具体元素:元素接口的各种实现类,重写accept()方法,方法体中调用访问者对象的visit方法(委派)
有一篇文章特别详细:link
总结
这一章说实话我最喜欢,还有一章就结束了