本文内容包含:装饰者模式、适配器模式、桥接模式、组合模式、外观模式、享元模式、代理模式
1 装饰者模式
该模式的4个基本角色:
- 基本抽象组件(Component):一个抽象类或者接口,由具体构件和抽象装饰者继承或实现
- 具体构件(ConcreteComponent):被装饰者
- 抽象装饰者(Decorator):装饰者的抽象类,属性中包含Component对象
- 具体装饰者(ConcreteDecorator):动态地为被装饰者添加额外功能
UML类图如下所示:
1.1 例子
需求:咖啡店的主要卖咖啡,但客户可以动态添加调料品(牛奶、糖等)
1、编写基本抽象组件
public abstract class Component {
public abstract String show();
}
2、编写被装饰者,即咖啡类
public class Coffee extends Component {
@Override
public String show() {
return "咖啡";
}
}
3、编写抽象装饰者和具体装饰者(牛奶、糖)
public abstract class Decorator extends Component {
private Component component;
public Component getComponent() {
return component;
}
public void setComponent(Component component) {
this.component = component;
}
}
public class Milk extends Decorator {
public Milk(Component component) {
this.setComponent(component);
}
@Override
public String show() {
return this.getComponent().show() + " 牛奶";
}
}
public class Sugar extends Decorator {
public Sugar(Component component) {
this.setComponent(component);
}
@Override
public String show() {
return this.getComponent().show() + " 糖";
}
}
4、编写测试类
public class Test {
public static void main(String[] args) {
Coffee coffee = new Coffee();
Milk milk = new Milk(coffee);
Milk milk2 = new Milk(milk);
Sugar sugar = new Sugar(milk2);
System.out.println(sugar.show());
}
}
1.2 总结
装饰者模式,可以不改动原有业务,在原有业务的基础上去扩展相关功能,达到开闭原则。Java中的IO流对象也使用了装饰者模式。
2 适配器模式
- 将一个类的接口转换成另一种接口,让原来接口不兼容的类可以兼容
- 用户是看不到被适配者(Source)
- 用户是调用适配器(Adapter)转换出来的目标(Dest)接口方法,适配器再调用被适配者的相关方法
- 可分为类适配器模式、对象适配器模式、接口(缺省)适配器模式
2.1 类适配器模式
2.1.1 例子
需求:一般家用电压为220V,但假设某个手机的充电电压为5V,则需要适配器来转换
1、编写目标接口
public interface IVoltage5V {
/**
* 目标接口方法
*/
int output5V();
}
2、编写被适配者
public class Voltage220V {
public int output220V() {
System.out.println("适配前电压为220V");
return 220;
}
}
3、编写适配器
public class Adapter extends Voltage220V implements IVoltage5V {
@Override
public int output5V() {
int srcV = output220V();
// 这里就相当于在做适配工作
int destV = srcV / 44;
return destV;
}
}
4、编写用户,即手机
public class Phone {
public void charge(IVoltage5V iVoltage5V) {
if (5 == iVoltage5V.output5V()) {
System.out.println("适配后电压为5V,可以充电");
} else {
System.out.println("适配后电压不为5V,不可以充电");
}
}
}
5、测试类
public class Test {
public static void main(String[] args) {
Phone phone = new Phone();
phone.charge(new Adapter());
}
}
2.1.2 总结
- 该模式中适配器需要继承Source类,且目标是接口,有一定的局限性
- Source类的方法会在Adapter中暴露出来,增加了使用成本
2.2 对象适配器模式
基本思路与类适配器相同,只是修改Adapter类,让它不再继承Source类,而是在属性中拥有Source类。
在上述例子的基础下进行修改:
1、修改适配器
public class Adapter implements IVoltage5V {
private Voltage220V voltage220V;
public Adapter(Voltage220V voltage220V) {
this.voltage220V = voltage220V;
}
@Override
public int output5V() {
if (null != voltage220V) {
int srcV = voltage220V.output220V();
// 这里就相当于在做适配工作
int destV = srcV / 44;
return destV;
}
return -1;
}
}
2、修改测试类
public class Test {
public static void main(String[] args) {
Phone phone = new Phone();
phone.charge(new Adapter(new Voltage220V()));
}
}
2.3 接口适配器模式
- 目标接口中不只一个方法
- 适配器是一个抽象类,实现目标接口,并重写所有接口方法,但都是空处理或简单处理
- 使用适配器时,需要哪个方法才具体重写那个方法
例子改进:
1、目标接口类
public interface IVoltage {
int output5V();
int output10V();
int output15V();
}
2、被适配者
public abstract class VoltageSource {
public abstract int output();
}
public class Voltage220V extends VoltageSource{
@Override
public int output() {
System.out.println("适配前电压为220V");
return 220;
}
}
3、适配器
public abstract class Adapter implements IVoltage {
private VoltageSource voltageSource;
public Adapter(VoltageSource voltageSource) {
this.voltageSource = voltageSource;
}
public VoltageSource getVoltageSource() {
return voltageSource;
}
@Override
public int output5V() { return -1; }
@Override
public int output10V() { return -1; }
@Override
public int output15V() { return -1; }
}
4、手机类
public class Phone {
public void charge(IVoltage iVoltage) {
if (5 == iVoltage.output5V()) {
System.out.println("适配后电压为5V,可以充电");
} else {
System.out.println("适配后电压不为5V,不可以充电");
}
}
}
5、测试类
public class Test {
public static void main(String[] args) {
Phone phone = new Phone();
phone.charge(new Adapter(new Voltage220V()) {
@Override
public int output5V() {
int srcV = this.getVoltageSource().output();
return srcV / 44;
}
});
}
}
UML类图如下所示:
3 桥接模式
当一个类需要在两个以上维度进行扩展时,如果采用继承方式会导致子类数量过多,于是引进桥接模式。该模式涉及的角色有:
- 抽象化(Abstraction)角色:抽象化给出的定义,并保存一个对实现化对象的引用。充当“桥”,把抽象化和实现化结合起来
- 修正抽象化(RefinedAbstraction)角色:扩展抽象化角色,改变和修正父类对抽象化的定义
- 实现化(Implementor)角色:这个角色给出实现化角色的接口,但不给出具体的实现。必须指出的是,这个接口不一定和抽象化角色的接口定义相同,实际上,这两个接口可以非常不一样。实现化角色应当只给出底层操作,而抽象化角色应当只给出基于底层操作的更高一层的操作
- 具体实现化(ConcreteImplementor)角色:这个角色给出实现化角色接口的具体实现
UML类图如下所示:
3.1 例子
需求:吃烧烤,可以分成食物和风味两个维度,如食物有鸡翅、鸡腿等;风味有微辣、特辣等。
1、编写实现化部分
public interface Taste {
String taste();
}
public class ExtraSpicy implements Taste {
@Override
public String taste() {
return "特辣";
}
}
public class SlightlySpicy implements Taste {
@Override
public String taste() {
return "微辣";
}
}
2、编写抽象化部分
public abstract class Food {
protected Taste taste;
// 通过这个构造方法,把抽象化和实例化组合起来
public Food(Taste taste) {
this.taste = taste;
}
public abstract void eat();
}
public class ChickenWings extends Food {
public ChickenWings(Taste taste) {
super(taste);
}
@Override
public void eat() {
System.out.println(super.taste.taste() + "鸡翅");
}
}
public class Drumsticks extends Food {
public Drumsticks(Taste taste) {
super(taste);
}
@Override
public void eat() {
System.out.println(super.taste.taste() + "鸡腿");
}
}
3、测试类
public class Test {
public static void main(String[] args) {
new ChickenWings(new ExtraSpicy()).eat();
new Drumsticks(new SlightlySpicy()).eat();
}
}
3.2 总结
当系统可能有多个角度分类,且每一种角度都可能变化,若此时仍使用继承则会造成类爆炸问题,扩展起来不灵活。这是可以使用桥接模式把这种多角度分类分离出来,让它们独立变化,减少它们之间耦合。但桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。
4 组合模式
组合模式在树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以像处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。主要有以下角色:
- 抽象构件(Component):可以是接口或者抽象类,是叶子构件和容器构件的接口,包含子类行为的声明和实现
- 叶子构件(Leaf):叶子节点没有子节点,是最低级的节点
- 容器构件(Composite):容器构件中含所有子节点,节点可以是子节点,也可以是容器节点,是一个集合,用来存储节点的。好比一个文件夹,文件夹好比一个容器,用来提供一个容器,它实现了抽象构件定义的行为方法,也可以调用子节点的方法
UML类图如下所示:
4.1 例子
需求:一个学校有多个学院,一个学院又有多个专业,用组合模式来管理
1、编写抽象构件
public abstract class Component {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void add(Component component) {
// 叶子节点没有对子节点的操作,所以不写成抽象方法
}
public void delete() {
// 叶子节点没有对子节点的操作,所以不写成抽象方法
}
public abstract void show();
}
2、编写大学类
public class University extends Component {
private List<Component> colleges = new ArrayList<>();
public University(String name) {
this.setName(name);
}
@Override
public void add(Component component) {
colleges.add(component);
}
@Override
public void delete() {
colleges.remove(0);
}
@Override
public void show() {
System.out.println("-----" + getName() + "-----");
for (Component c : colleges) {
c.show();
}
}
}
2、编写学院类
public class College extends Component {
private List<Component> majors = new ArrayList<>();
public College(String name) {
this.setName(name);
}
@Override
public void add(Component component) {
majors.add(component);
}
@Override
public void delete() {
majors.remove(0);
}
@Override
public void show() {
System.out.println("--" + getName() + "--");
for (Component c : majors) {
c.show();
}
}
}
3、编写专业类
public class Major extends Component {
public Major(String name) {
this.setName(name);
}
@Override
public void show() {
System.out.println(getName());
}
}
4、编写客户端
public class Client {
public static void main(String[] args) {
University university = new University("XX大学");
College college1 = new College("计通学院");
college1.add(new Major("计科专业"));
college1.add(new Major("网络专业"));
college1.add(new Major("软件专业"));
College college2 = new College("土木学院");
college2.add(new Major("土木专业"));
college2.add(new Major("材料专业"));
college2.add(new Major("建筑专业"));
university.add(college1);
university.add(college2);
university.show();
}
}
4.2 总结
组合模式的使用场景:
- 想表示对象的部分-整体层次结构(树形结构)
- 用户忽略组合对象与单个对象的不同,可以统一地使用组合结构中的所有对象
5 外观模式
外观模式(Facade Pattern)隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口。这种类型的设计模式属于结构型模式,它向现有的系统添加一个接口,来隐藏系统的复杂性。
5.1 例子
电脑开机其实是复杂的过程,启动CPU、内存、硬盘等等,但是用户不需要知道,用户只需简单按一下电源键即可,这其实就是外观模式。
1、电脑组件类
public interface Component {
void open();
}
public class CPU implements Component {
@Override
public void open() {
System.out.println("启动CPU");
}
}
public class Memory implements Component {
@Override
public void open() {
System.out.println("启动内存");
}
}
public class SSD implements Component {
@Override
public void open() {
System.out.println("启动固体硬盘");
}
}
2、电脑类
public class Computer {
private CPU cpu = new CPU();
private Memory memory = new Memory();
private SSD ssd = new SSD();
public void open() {
cpu.open();
memory.open();
ssd.open();
}
}
3、客户端
public class Client {
public static void main(String[] args) {
Computer computer = new Computer();
computer.open();
}
}
5.2 总结
- 客户端不需要知道系统内部的复杂联系,整个系统只需提供一个"接待员"即可
- web开发的MVC架构其实就使用了外观模式
6 享元模式
享元模式是对象池的一种实现。将多个对同一对象的访问集中起来,不必为每个访问者创建一个单独的对象,以此来降低内存的消耗。
享元模式把一个对象的状态分成内部状态和外部状态,内部状态即是不变的,外部状态是变化的;然后通过共享不变的部分,达到减少对象数量并节约内存的目的。
6.1 例子
模拟车站买票,买票前,很多人都会查是否有票,而这些票的信息(出发地、目的地等)都相同,若频繁地创建相似对象会浪费资源,这时就可以使用享元模式。
1、票类
public interface Ticket {
void show();
}
public class TrainTicket implements Ticket {
private String from;
private String to;
public TrainTicket(String from, String to) {
this.from = from;
this.to = to;
}
@Override
public void show() {
System.out.println("出发地:" + from + " 目的地:" + to);
}
}
2、车站类
public class Station {
private Map<String, Ticket> tickets;
private static Station instance;
private Station() {
this.tickets = new HashMap<>();
}
public static Station getInstance() {
if (null == instance) {
instance = new Station();
}
return instance;
}
public Ticket getTicket(String from, String to) {
String key = from + ":" + to;
if (tickets.containsKey(key)) {
return tickets.get(key);
} else {
Ticket ticket = new TrainTicket(from, to);
tickets.put(key, ticket);
return ticket;
}
}
}
3、用户
public class Client {
public static void main(String[] args) {
Station station = Station.getInstance();
Ticket ticket1 = station.getTicket("深圳", "广州");
Ticket ticket2 = station.getTicket("深圳", "广州");
ticket1.show();
System.out.println(ticket1 == ticket2);
}
}
6.2 总结
- 享元模式可以极大减少内存中对象的数量,使得相同对象或相似对象在内存中只保存一份,降低内存占用,增强程序的性能
- 享元模式的外部状态相对独立,而且不会影响其内部状态,从而使得享元对象可以在不同的环境中被共享
7 代理模式
为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用,且代理对象可以在不修改目标对象方法的前提下对其方法进行扩展。可分为静态代理、动态代理、Cglib代理。
比如:我们想听明星唱歌,一般是不能接触到明星请他唱的,经常是通过他的经理来进行预约的,这里的经理就起到代理的作用。
7.1 静态代理
1、目标类
/*
这个接口是目标对象和代理对象都要实现的
*/
public interface Singer {
void sing();
}
public class JayChou implements Singer {
@Override
public void sing() {
System.out.println("周杰伦唱歌...");
}
}
2、代理类
public class SingerProxy implements Singer {
private Singer singer;
public SingerProxy(Singer singer) {
this.singer = singer;
}
@Override
public void sing() {
System.out.println("演唱前的预约工作...");
singer.sing();
System.out.println("演唱后的收尾工作...");
}
}
3、测试类
public class Test {
public static void main(String[] args) {
SingerProxy singerProxy = new SingerProxy(new JayChou());
singerProxy.sing();
}
}
7.2 动态代理
1、目标类
public interface Star {
void sing();
void act();
}
public class JackyCheung implements Star{
@Override
public void sing() {
System.out.println("张学友唱歌...");
}
@Override
public void act() {
System.out.println("张学友拍戏...");
}
}
2、自定义事件处理器
public class MyInvocationHandler implements InvocationHandler {
private Star star;
public MyInvocationHandler(Star star) {
this.star = star;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("sing")) {
System.out.println("预约明星唱歌..");
Object returnValue = method.invoke(star, args);
System.out.println("收尾工作...");
return returnValue;
} else {
System.out.println("预约明星拍戏..");
Object returnValue = method.invoke(star, args);
System.out.println("收尾工作...");
return returnValue;
}
}
}
3、客户端
public class Client {
public static void main(String[] args) {
MyInvocationHandler myInvocationHandler = new MyInvocationHandler(new JackyCheung());
/*
Proxy.newProxyInstance的三个参数
1.ClassLoader loader:目标对象使用的类加载器,获取加载器的方法是固定的
2.Class<?>[] interfaces:目标对象实现的接口的类型
3.InvocationHandler h:事件处理器,当执行目标对象的方法时,会触发事件处理器的方法
*/
Star starProxy = (Star) Proxy.newProxyInstance(Star.class.getClassLoader(),
new Class[]{Star.class},
myInvocationHandler);
starProxy.sing();
starProxy.act();
}
}
7.3 Cglib代理
Cglib代理可以算作是动态代理的一种,但是它的目标对象不需要实现接口。
1、导入依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
2、目标类
public class JackyCheung {
public void sing() {
System.out.println("张学友唱歌...");
}
public void act() {
System.out.println("张学友拍戏...");
}
}
3、代理工厂
public class ProxyFactory implements MethodInterceptor {
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("cglib代理开始...");
Object returnValue = method.invoke(target, args);
System.out.println("cglib代理结束...");
return returnValue;
}
/**
* 返回target的代理对象
*/
public Object getProxyInstance() {
// 1.创建工具类
Enhancer enhancer = new Enhancer();
// 2.设置父类
enhancer.setSuperclass(target.getClass());
// 3.设置回调函数
enhancer.setCallback(this);
// 4.创建子类对象,即代理对象
return enhancer.create();
}
}
4、客户端
public class Client {
public static void main(String[] args) {
ProxyFactory proxyFactory = new ProxyFactory(new JackyCheung());
JackyCheung proxyInstance = (JackyCheung) proxyFactory.getProxyInstance();
proxyInstance.act();
proxyInstance.sing();
}
}