设计模式-结构型模式

适配器模式

简介

  • 将一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。
  • 适配器模式,定义一个包装类,用于包装不兼容接口的对象
  • 把一个类的接口变换成客户端所期待的另一种接口,从而使原本接口不匹配而无法一起工作的两个类能够在一起工作。适配器的模式的形式分别:为类的适配器和对象的适配器
    包装类=适配器Adapter
    被包装类对象 = 适配器Adaptee = 被适配的类

解决问题

  • 原版由于接口不兼容而不能一起工作的那些类可以一起工作。
  • 透明简单,客户端可以调用同一个接口,因而对客户端来说是透明的
  • 复用性,需要实现现有的类,此类的接口不符合系统的需要,那么通过适配器模式可以让这些功能得到更好的复用
  • 扩展性,实现适配器功能的时候,通过调用自己开发的功能,从而自然的扩展系统的功能
  • 解耦性,将目标类和适配者类解耦,通过引入一个适配器类重用现有的适配者类,而无需修改源码
  • 符合开闭原则,同一个适配器可以把适配者类和它的子类都适配到目标接口;可以为不同的目标接口实现不同的适配器,而不需要修改适配类。

    UML类图

img

举例实例

public interface Target {

//这是源类Adapteee没有的方法
public void Request();
}
public class Adaptee {

public void SpecificRequest(){
}
}
//适配器Adapter继承自Adaptee,同时又实现了目标(Target)接口。
public class Adapter extends Adaptee implements Target {

//目标接口要求调用Request()这个方法名,但源类Adaptee没有方法Request()
//因此适配器补充上这个方法名
//但实际上Request()只是调用源类Adaptee的SpecificRequest()方法的内容
//所以适配器只是将SpecificRequest()方法作了一层封装,封装成Target可以调用的Request()而已
@Override
public void Request() {
this.SpecificRequest();
}

}
public class AdapterPattern {

public static void main(String[] args){

Target mAdapter = new Adapter();
mAdapter.Request();

}
}

问题

  • 过多的使用适配器会让系统非常凌乱,不易整体进行把握。

应用

  • 系统需要复用现有类,而该类接口不符合系统的需求,可以使用适配器模式使得原本由于接口不兼容而不能一起工作的类一起工作。
  • 多个组件功能类似,接口不统一且可能会经常切换时候,可以使用适配器模式,使得客户端额可以统一的接口使用他们。

桥接模式

简介

  • 抽象部分与它的实现部分分离,使它们都可以独立的变化。
  • 抽取其中一个维度并使之成为独立的类层次
  • 在某个类中添加一个指向某一属性对象的引用成员变量。

模式组成

组成 作用
抽象类 定义抽象类的接口,定义了一个Implementor实现类接口的对象斌可以维护其对象
提炼抽象类 扩充抽象类定义的接口,通常属于具体类,实现抽象类中声明的抽象业务方法,在提炼抽象类中,能够调用在实现类接口的业务方法
实现类接口 这个接口不一定要与抽象类的接口完全一致,事实上这两个接口可以完全不同,可以仅仅提供基本操作,而抽象类接口可以实现更多复杂的操作。
具体实现类 具体实现实现类接口,在不同的具体实现类中提供基本操作的不同实现,在程序运行时候,具体实现类的对象将会替换其父类对象,提供给抽象类具体的业务操作方法。

解决问题

  • 提高系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统
  • 有的时候类似于多继承方案,但是多继承方案违背了类的单一职责原则,复用性比较差,而且多继承的结构类中的个数非常庞大,桥接模式是比多继承方案更好的解决方法。

UML类图

在这里插入图片描述

举例实例

//用于画各种颜色的接口
public interface ColorAPI{
public void paint();
}
public class BlueColorAPI implements ColorAPI{
@Override
public void paint(){
System.out.println("蓝色");
}
}
public class RedColorAPI implements ColorAPI{
@Override
public void paint(){
System.out.println("红色");
}
}
public abstract class Shape{
protected ColorAPI colorAPI;
public void setDrawAPI(ColorAPI colorAPI){
this.colorAPI = colorAPI;
}
public abstract void draw();
}
public class Circle extends Shape{
@Override
public void draw(){
System.out.print("圆形");
colorAPI.paint();
}
}
public class Rectangle extends Shape{
@Override
public void draw(){
System.out.print("长方形");
colorAPI.paint();
}
}
public class Client{
public static void main(String[]args){
//创建一个圆形
Shape shape = new Circle();
//给圆形蓝色的颜料
shape.setDrawAPI(new BlueColorAPI());
//上色
shape.draw();
//创建一个长方形
Shape shape1 = new Rectangle();
//给长方形上红色的颜料
shape1.setDrawAP(new RedColorAPI());
//上色
shape1.draw();
}
}

如果这个时候客户需要一个绿色的三角形那么只需要新增一个三角形类即可。

public class Triangle extends Shape{
@Override
public void draw(){
System.out.println("三角形");
colorAPI.paint();
}
}
public class GreenColorAPI implements ColorAPI{
@Override
public void paint();
System.out.println("绿色");
}
public class Client{
public static void main(String [] args){
Shape shape = new Triangle();
shape.setDrawAPI(new GreenColorAPI());
shape.draw();
}
}

问题

  • 桥接模式的引入会增加系统的理解与设计的难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计以及编程
  • 桥接模式要求正确的识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性。

装饰器模式

简介

  • 不改变现有对象结构的情况下,动态地给对象增加一些职责的模式,它属于对象结构型模式

解决问题

  • 装饰器是继承的有力补充,比继承灵活,在不改变原有对象的情况下,动态地给一个对象扩展功能,即插即用。
  • 通过不用装饰类以及这些装饰类的排列组合,可以实现不同效果
  • 装饰器模式完全遵守开闭原则

模式组成

组成 作用
抽象构件 定义一个抽象接口以规范准备接收附加责任的对象
具体构件 实现抽象构件,通过装饰角色为其添加一些职责
抽象装饰 继承抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能
具体装饰 实现抽象装饰的相关方法,并给具体构件对象添加附加责任。

UML类图

image-20230329003915296

举例实例

public class DecoratorPattern {
public static void main(String[] args) {
Component p = new ConcreteComponent();
p.operation();
System.out.println("---------------------------------");
Component d = new ConcreteDecorator(p);
d.operation();
}
}
//抽象构件角色
interface Component {
public void operation();
}
//具体构件角色
class ConcreteComponent implements Component {
public ConcreteComponent() {
System.out.println("创建具体构件角色");
}
public void operation() {
System.out.println("调用具体构件角色的方法operation()");
}
}
//抽象装饰角色
class Decorator implements Component {
private Component component;
public Decorator(Component component) {
this.component = component;
}
public void operation() {
component.operation();
}
}
//具体装饰角色
class ConcreteDecorator extends Decorator {
public ConcreteDecorator(Component component) {
super(component);
}
public void operation() {
super.operation();
addedFunction();
}
public void addedFunction() {
System.out.println("为具体构件角色增加额外的功能addedFunction()");
}
}

问题

  • 装饰器模式会增加许多子类,过度使用会增加程序的复杂性。

    代理模式

    简介

  • 为其他对象提供一种代理以控制这个对象的访问。

解决问题

  • 职责清晰
  • 高扩展,只要实现了接口,都可以用代理
  • 智能化,动态代理。

UML图

image-20230329010025796

举例实例

静态代理

  1. 以租房为例,我们一般用租房软件、找中介或者找房东。这里的中介就是代理者。
    //定义一个提供了租房方法的接口
    public interface IRentHouse {
    void rentHouse();
    }
    //定义租房的实现类
    public class RentHouse implements IRentHouse {
    @Override
    public void rentHouse() {
    System.out.println("租了一间房子。。。");
    }
    }
    //租房找中介
    public class IntermediaryProxy implements IRentHouse {

    private IRentHouse rentHouse;

    public IntermediaryProxy(IRentHouse irentHouse){
    rentHouse = irentHouse;
    }

    @Override
    public void rentHouse() {
    System.out.println("交中介费");
    rentHouse.rentHouse();
    System.out.println("中介负责维修管理");
    }
    }
    public class Main {

    public static void main(String[] args){
    //定义租房
    IRentHouse rentHouse = new RentHouse();
    //定义中介
    IRentHouse intermediary = new IntermediaryProxy(rentHouse);
    //中介租房
    intermediary.rentHouse();
    }
    }

    组合模式

    简介

    又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次,他创建了对象组的树形结构

    模式组成

角色 解释
抽象构件 为树叶构件和树枝构件声明公共接口,实现默认行为
树叶构件 没有子节点,用于继承或实现抽象构件,是树状结构最底层。
树枝构件 有子节点,是组合中的分支节点。

解决问题

  • 一致处理单个对象和组合对象,无须关心处理的是单个对象还是组合对象。
  • 容易在组合体内加入新的对象,客户端不会因为加入了新的对象而更改源代码。

问题

  • 设计复杂,需要一定时间理清层次关系
  • 不容易限制容器中的构件
  • 不容易用继承的方法来增加构件的新功能

举例实现

//抽象构件
/**
* 这里使用接口或者抽象类都可以的
**/
public abstract class Region {
/**
* 添加节点
* @param region
*/
abstract void add(Region region);

/**
* 删除节点
* @param region
*/
abstract void remove(Region region);

/**
* 获取当前节点下面的节点
* @param i
* @return
*/
abstract Region getChild(int i);
/**
* 通知,上面下个政策一级一级的传递,一般都有一个动作方法
*/
abstract void notice();
}
/**
* 树叶地区
**/
public class LeafRegion extends Region{

private String name;

public LeafRegion(String name) {
this.name = name;
}

@Override
void add(Region region) {
//叶子节点没有下级
}

@Override
void remove(Region region) {
//叶子节点没有下级
}

@Override
Region getChild(int i) {
//叶子节点没有下级
return null;
}

@Override
void notice() {
/**
* 最底层的接到了通知
*/
}
}
/**
* 树枝地区
**/
public class CompositeRegion extends Region{

private String name;

//用来盛放子节点
private List<Region> children = new ArrayList<>();

@Override
void add(Region region) {
children.add(region);
}

@Override
void remove(Region region) {
children.remove(region);
}

@Override
Region getChild(int i) {
return null;
}

@Override
void notice() {
/*通知下级所有的部门,下级如果是树枝的话继续通知,这是个递归操作*/
for (Region child : children) {
child.notice();

}
}
}

外观模式

简介

  • 为子系统的一组接口提供一个一致的界面,定义了一个高层接口,这个接口使得这一子系统更加容易使用

模式组成

角色类型 作用解释
外观(Facade) 封装系统底层功能,为客户端提供简单易用的接口
子系统类(SubSystem) 提供系统底层的具体实现细节
客户端(Client) 调用外观对象提供的简单接口,使用系统底层功能而无需了解其具体实现

解决问题

  • 降低访问复杂系统的内部子系统时的复杂度,简化个护短之间的接口。
  • 减少系统的互相依赖,提高灵活性,提高安全性。

    问题

  • 不符合开闭原则

举例实现

  1. 假设你现在需要购买一台电脑,在购买这个过程中,有很多不同的步骤需要完成,如选择操作系统、购买CPU、选择显示屏尺寸等等。然而,如果你并不关心电脑内部如何工作,而只关注它的整体性能和使用方式,那么外观模式就可以被用来简化这个过程。

具体地说,一个电脑厂商可能会实现一个电脑购买外观(Facade),它将 CPU 购买、内存配置、显示器选择、操作系统安装等操作封装在一起,提供一个简单易用的接口让用户来购买电脑。对于客户端而言,只需要调用外观对象提供的购买接口就可以了,而不必了解具体的硬件和软件实现细节,大大简化了购买电脑的流程。当内部实现发生变化时,只需修改外观类即可,对客户端代码没有影响。

public class ComputerPurchaseFacade {

private CPU cpu;
private Memory memory;
private Display display;
private OS os;

public ComputerPurchaseFacade() {
cpu = new CPU();
memory = new Memory();
display = new Display();
os = new OS();
}

public void buyComputer(int cpuType, int memorySize, int displaySize, int osType) {
cpu.selectCPU(cpuType);
memory.setMemorySize(memorySize);
display.selectDisplay(displaySize);
os.installOS(osType);
System.out.println("Your computer has been purchased!");
}
}

class CPU {
public void selectCPU(int type) {
// 选择不同种类的CPU
}
}

class Memory {
public void setMemorySize(int size) {
// 配置内存大小
}
}

class Display {
public void selectDisplay(int size) {
// 选择不同尺寸的显示器
}
}

class OS {
public void installOS(int type) {
// 安装不同类型的操作系统
}
}

public class Client {
public static void main(String[] args) {
ComputerPurchaseFacade facade = new ComputerPurchaseFacade();
facade.buyComputer(1, 8, 15, 2);
}
}

应用场景

  1. 为一个复杂的子系统提高逻辑支持
  2. 调用多个子系统完成逻辑

享元模式

简介

  • 有两类对象:共享的享元对象和非共享的外部状态对象。享元对象包含内部状态和外部状态两部分,其中内部状态是不变的,可以被多个享元对象共享;外部状态是变化的,不能被共享,每个对象都需要单独维护。
  • 将原本需要大量创建的相似对象合并为较少的共享对象,这样可以节省内存空间,并提高程序的运行效率。

解决问题

  • 减少系统内部资源开销,通过对象共享,减少系统创建对象的数量,降低内存的开销
  • 提高系统性能,减少垃圾回收机制的次数

问题

  • 对象共享会导致程序逻辑复杂化。原来对象自己拥有的内部状态和外部状态现在需要从外部导入。
  • 对象共享是有限制的,即那些可以共享的对象需要满足一定的条件,否则无法实现对象的共享。

模式组成

角色类型 作用解释
抽象享元角色(Flyweight) 定义享元对象的接口及需要缓存的数据,充当所有具体享元类的基类。
具体享元角色(ConcreteFlyweight) 实现抽象享元角色所定义的接口,同时需要为内部状态增加存储空间。并且可以接受外部状态(容易变化的状态),并根据外部状态进行相应的业务逻辑处理。
非共享具体享元角色(UnsharedConcreteFlyweight) 通常不会出现单独的非共享具体享元角色,因为非共享具体享元角色与单纯的享元模式没有什么区别。
享元工厂角色(FlyweightFactory) 提供一个用于管理享元对象的工厂类。主要用于享元对象的创建和缓存,实现对象的复用,减少对象的创建次数,节省内存空间。
客户端角色(Client) 通过享元工厂角色获取具体的享元角色,并访问具体享元角色中的相关业务方法。

举例实现

  1. 我们正在开发一个棋类游戏,游戏中有大量的棋子需要被使用。不同的棋子有不同的颜色和形状,但是棋子的功能(如移动、吃子等)都是一样的。我们可以复用相同颜色和形状的棋子对象,避免重复创建棋子导致内存资源的浪费,提高系统的性能。
//棋子的共享接口
public interface ChessPiece {
void setPosition(int x, int y);
void draw();
}

//维护了颜色和形状的内部状态,位置等可变状态外部传入
public class ConcreteChessPiece implements ChessPiece {
private String color;
private String shape;

public ConcreteChessPiece(String color, String shape) {
this.color = color;
this.shape = shape;
}

@Override
public void setPosition(int x, int y) {
System.out.println(String.format("Set position for %s %s chess to (%d,%d)", color, shape, x, y));
}

@Override
public void draw() {
System.out.println(String.format("Draw %s %s chess", color, shape));
}
}

//管理棋子对象并进行复用。
import java.util.HashMap;
import java.util.Map;

public class ChessPieceFactory {
private static Map<String, ChessPiece> chessPieces = new HashMap<>();

public static ChessPiece getChessPiece(String color, String shape) {
String key = color + shape;
if (chessPieces.containsKey(key)) {
return chessPieces.get(key);
} else {
ChessPiece piece = new ConcreteChessPiece(color, shape);
chessPieces.put(key, piece);
return piece;
}
}
}

//客户端
for (int i = 1; i <= 10; i++) {
ChessPiece piece = ChessPieceFactory.getChessPiece("red", "circle");
piece.setPosition(i, 0);
piece.draw();
}