原创
最近更新: 2022/10/17 17:04

设计模式 (二) 结构型模式

设计模式 | 菜鸟教程

黑马程序员Java设计模式详解

目录

设计模式 (一) 创建型模式

设计模式 (二) 结构型模式

设计模式 (三) 行为型模式


代理模式

在某些情况下,访问者不适合直接访问目标对象,此时用一个代理对象作为访问者和目标对象的中介。

代理能够访问、控制或扩展目标对象的功能,访问者通过代理来操作目标对象。

包含以下组件:

  • 抽象主题类:包含真实主题和代理对象实现的业务方法的接口。
  • 真实主题类:目标对象的具体实现。
  • 代理类:一般含有对真实主题的引用。
//iOS开发中经常使用该模式,View常常将ViewController设置为代理,让VC来控制自己的行为。又称为委托。
//用Java的语法写一下iOS中页面代理的大致框架

//主题类
public class TableView {

    //以下是两种重要的代理
    public id<TableViewDataSource> dataSource;  //用于读取数据
    public id<TableViewDelegate> delegate;    //用于管理生命周期

    public TableView(Frame frame) {...}
}

//代理类
public class ViewController implements TableViewDelegate, TableViewDataSource {

    public TableView tableView;
    //其他的用于管理TableView的方法
    private Cell getCellAtIndex(TableView tableView, CellIndex index) {...}
}

Java提供了使用反射创建代理的方法,省去了自己实现代理的步骤,代码略。

Spring中的面向切面编程也是通过代理模式实现的。

优点:可以隔绝对于目标对象不安全的访问,同时将业务逻辑实现在代理中可以降低目标对象的复杂度,降低系统的耦合度。

缺点:提高系统复杂度


适配器模式

将一个类的接口转换成用户需要的另外一种接口,来提高类的兼容性。

分为类适配器和对象适配器,前者通过继承实现,耦合度更高,且需要程序员对组件库有相当的了解;后者通过聚合实现,使用更多。

常用于底层数据结构与高层数据结构的转换,如用列表实现栈;或者进行不同组件或系统的转换,如将安卓操作系统包装之后变成鸿蒙等。

主要包含以下组件

  • 目标接口:用户需要的接口。
  • 适配者类:现存的,不适应于用户的组件。
  • 适配器类:通过引用适配者对象,将适配者的接口转为目标接口。
//使用列表实现栈
//目标接口,规定了栈所需要的各种方法
public interface Stack<E> {
    void push(E e);
    E pop();
    E getTop();
    int getSize();
    boolean isEmpty();
}

//==========================

//适配器类,使用列表来实现接口中的方法
public class LinkedListStack<E> implements Stack<E>{

    private LinkedList<E> list;//Java内建的列表

    public LinkedListStack(){
        list = new LinkedList<E>();
    }

    public void push(E e) {
        list.addFirst(e);
    }
    public E pop() {
        return list.removeFirst();
    }
    public E getTop() {
        return list.getFirst();
    }
    public int getSize() {
        return list.getSize();
    }
    public boolean isEmpty() {
        return list.isEmpty();
    }
}

装饰器模式

在不改变现有对象结构的情况下,动态地给对象添加额外功能或者组件。

包含以下组件:

  • 抽象被装饰类:定义一个抽象接口规范,以接收可能的附加组件。

  • 具体被装饰类:需要额外添加功能的类。

  • 抽象饰品类:继承或者实现抽象构件,且包含具体构件的实例。

  • 具体饰品类:用于给具体构件添加额外的功能组件。

应该注意的是,这里的构件类和装饰类其实都是抽象构件类的子类,在语义上,“装饰”并不只意味着装饰本身,而是指“受到装饰的构件”。

因此,在具体实现的结构上,最终的结构就是一个具体的构件被一层一层的装饰所包裹,且每一层都是一个完整的构件对象。获取属性时,程序会递归地从最外层往中间计算。

/*
代码结构:
		类间继承结构:[Charactor] ← Hero
								↖ [Weapon] ← Sword
		对象持有结构:[Sword[Hero]] = [Weapon[Hero]] = [Charactor[Charactor]]
		装饰器模式的对象结构可以无限扩展:[Weapon[Weapon[Weapon[...[Hero]]]]]
*/

//游戏中的角色类,属于抽象被装饰类,包含描述和攻击力两个属性
public abstract class Charactor {

    private String desc; //角色描述
    private float attack;//角色攻击力

    public Charactor() {}

    public Charactor(String desc, float attack) {
        this.desc = desc;
        this.attack = attack;
    }

    public String getDesc() {
        return desc;
    }

    public float getAttack() {
        return attack;
    }

    public abstract float damage();//计算角色伤害
}

//----------------------------
//主人公角色,属于具体被装饰类
public class Hero extends Charactor {

    public Hero() {
        //在无参构造中设定基础属性
        super("主人公", 100);
    }

    @Override
    public float damage() {
        //计算伤害时直接返回攻击力即可
        return getAttack();
    }
}

//============================
//武器类,属于抽象饰品类
public abstract class Weapon extends Charactor {

    //需要声明一个被修饰的角色变量
    private Charactor charactor;

    public Weapon(Charactor charactor, String desc, float attack) {
        super(desc, attack);
        this.charactor = charactor;
    }

    public Charactor getCharactor() {
        return charactor;
    }

    public void setCharactor(Charactor charactor) {
        this.charactor = charactor;
    }
}

//----------------------------
//剑,属于具体饰品类
public class Sword extends Weapon {

    public Sword(Charactor charactor) {
        super(charactor, "剑", 50);
    }

    @Override
    public float damage() {
        //递归地计算伤害
        return getCharactor().damage() + getAttack();
    }

    @Override
    public String getDesc() {
        //递归地获取描述
        return getCharactor().getDesc() + "+" + super.getDesc();
    }
}

//=====================
//调用方法:
Charactor princess = new Hero();
princess = new Sword(princess);

优点

  • 可以带来比继承更加灵活的扩展功能,可以通过组合不同的装饰者对象获取多样化的结果。完美遵循开闭原则。
  • 装饰类和被装饰类可以独立发展,不会相互耦合

使用场景

  • 不能用继承的方式对系统进行扩展时,如扩展类数量巨大,或被继承的类用final修饰
  • 需要独立地对一个对象动态地添加功能
  • 需要动态地撤销功能时

桥接模式

将抽象与实现分离,使用组合关系代替继承关系,降低程序耦合性。与装饰器模式相近。

包含以下组件:

  • 抽象属性:定义实现化角色的接口,供中心角色调用。

  • 具体属性:实现上述接口。

  • 抽象中心角色:定义抽象类,并包含一个对抽象属性的引用。可以理解为操作属性的用户。

  • 扩展中心角色:是抽象中心角色的子类,实现父类中的业务方法,并通过组合关系调用具体属性中的业务方法。

/*
类间结构:
	[Charactor]---持有--→[Weapon]
		↑				    ↑
	  Hero	   ---持有--→  Sword
*/

//抽象属性
public interface Weapon {
    public void showWeapon(String weaponName);
}
//-------------------------
//具体属性
public class Sword implements Weapon{
    @Override
    public void showWeapon(String weaponName) {
        System.out.println("剑:" + weaponName);
    }
}

//=========================
//抽象中心角色
public abstract class Charactor {

    protected Weapon weapon;

    public Charactor(Weapon weapon) {
        this.weapon = weapon;
    }

    public abstract void getWeapon(String weaponName);
}
//-------------------------
//扩展中心角色
public class Hero extends Charactor{

    public Hero(Weapon weapon) {
        super(weapon);
    }

    @Override
    public void getWeapon(String weaponName) {
        this.weapon.showWeapon(weaponName);
    }
}
//=====================
//调用方法:
Charactor princess = new Hero(new Sword());
princess.getWeapon("excalibur");

好处:提高系统的可扩展性,降低耦合度,在两个或者多个维度中可以任意扩展而不影响其他维度。避免直接用静态的继承实现导致类爆炸。

使用场景:当一个类存在两个或多个独立变化的维度时适用。

与装饰器模式的差别

  • 装饰器模式中,核心对象类(被装饰类)和饰品类是同一个父类的子类。

  • 桥接模式中,中心角色类和属性类之间没有任何继承或者实现上的关系。

  • 装饰器模式中,饰品并没有一个明确的分类,多个同类饰品可以叠加使用,饰品可以按需求无限叠加。

  • 桥接模式中,属性类可以分为多个独立的品类,每个品类有自己的变化,同一品类内是唯一的,中心角色持有的属性数量是固定的。

  • 用游戏装备的例子类比,装饰器模式像是MOBA,英雄可以出许多相同的武器;桥接模式像是RPG,同一类装备只能有一个。


外观模式

为多个复杂的子系统提供一个一致的接口,使得这些子系统更容易被访问。外部程序不需要了解子系统的具体细节,降低程序的复杂度,提高程序的可维护性。

是最少知识原则的典型应用。

案例:基金与股票、债券、黄金等;智能家居管理APP;许多框架底层实现非常复杂,但是使用接口很简单。

包含以下结构:

  • 外观类:为多个子系统提供一个共同的接口,通过持有子系统来调用子系统的公共方法,并且处理不同子系统之间的操作差异。
  • 子系统类:实现系统的部分功能,客户通过外观来间接访问。

代码略。

优点:降低子系统与外部程序之间的耦合,使客户用起来更方便,同时使客户与子系统隔离,保证安全性。

缺点:不符合开闭原则,修改起来比较麻烦。


组合模式

又名部分整体模式。即以一定方式将若干相似对象组合成一个整体,使得我们能够像对待单一对象一样对待这个整体。一般组织为一个树形结构,并以一定方式平等地对待叶子节点和非叶子节点。

包含以下结构:

  • 抽象节点:定义系统各层次对象的共有方法和属性,树枝节点和叶子节点都要实现抽象节点的方法,方便外部调用。
  • 树枝节点:也叫非叶子节点,是抽象节点的子类。
  • 叶子结点:也是抽象节点的子类。

代码略。

应当注意的是,叶子节点和树枝节点在本质上是不同的,所以适用于树枝节点的方法未必适用于叶子节点。如果叶子节点去实现那些只适用于树枝节点的方法,就需要错误处理。

也可以退一步,只在抽象类中声明普遍使用的方法,特殊方法则只在子类中实现。这样的缺点是不再面向抽象编程,系统实现不再透明。


享元模式

通过缓存和共享已经存在的对象来大幅度减少需要创建的对象的数量,从而提高系统的资源利用率。

存在以下两种状态:

  • 内部状态:不会随环境改变的可共享部分。
  • 外部状态:不可共享的部分。

享元模式的实现就是要区分这两种状态,并进行分别设计。

包含以下组件:

  • 抽象享元:通常是一个接口或抽象类,定义了可共享部分的公共方法,用于向外部提供对象的内部数据,同时也可以设置外部数据。
  • 具体享元:实现具体享元类,成为享元对象。具体享元类存储了内部状态,通常可以结合单例模式设计。
  • 非享元:存储外部状态的对象,可以直接被实例化。也可以不设计这类对象,外部状态仅作为参数传入对应方法。
  • 享元工厂:负责创建和管理享元。当客户请求一个享元对象时,享元工厂检查缓存中是否有对应的享元对象,有则直接提供,否则创建一个新的。

适用于系统中会大规模复用同一类对象的场景。

  • Java中的Integer类使用了享元模式,[-128, 127]中的整数会被缓存。

  • 手机APP的列表组件,当用户快速滑动浏览时,会涉及大量单元格的构造和析构,消耗大量系统资源;而使用一个缓存池将划出屏幕单元格储存起来,新进入屏幕的单元格直接从缓存池中取用,则可以极大减少系统的资源消耗。

优点:节省系统资源。

缺点:程序逻辑复杂。


过滤器模式

模式允许开发人员使用不同的标准来过滤一组对象,通过逻辑运算以解耦的方式把它们连接起来。

其实就是将一般的布尔表达式对象化,来实现对象和逻辑的解耦。

包含以下组件:

  • 被过滤的对象类

  • 抽象过滤器:定义了过滤器共有的方法,输入和输出一般是一个列表。

  • 具体过滤器:抽象过滤器的具体实现,设定过滤条件。

  • 逻辑过滤器:抽象过滤器的具体实现,将不同的具体过滤器进行连接,包括与或非等逻辑运算,实现较具体过滤器复杂。

//假设有一个Person类,包含年龄和性别维度,我们要筛选出成年女性。
public class Person {

    public static enum Gender {
        male,
        female;
    }

    public Gender gender;
    public int age;

    public Person(Gender gender, int age) {
        this.gender = gender;
        this.age = age;
    }
}

//============================
//抽象过滤器
public interface Filter {
    public abstract List<Person> check (List<Person> people);
}

//具体过滤器,过滤性别条件
public class FemaleFilter implements Filter{
    @Override
    public List<Person> check(List<Person> people) {
        List<Person> ret = new LinkedList<Person>();
        for (Person p: people) {
            if (p.gender == Person.Gender.female) {
                ret.add(p);
            }
        }
        return ret;
    }
}
//具体过滤器,过滤年龄条件
public class OlderThan18Filter implements Filter{
    @Override
    public List<Person> check(List<Person> people) {
        List<Person> ret = new LinkedList<Person>();
        for (Person p: people) {
            if (p.age >= 18) {
                ret.add(p);
            }
        }
        return ret;
    }
}
//逻辑过滤器,与逻辑
public class AndFilter implements Filter {

    private Filter one;
    private Filter other;

    public AndFilter(Filter one, Filter other) {
        this.one = one;
        this.other = other;
    }

    @Override
    public List<Person> check(List<Person> people) {
        List<Person> ret = this.one.check(people);
        return this.other.check(ret);
    }
}

//======================
//调用方法
LinkedList<Person> people = new LinkedList<>(...);
Filter filter = new AndFilter(new FemaleFilter(), new OlderThan18Filter());
LinkedList<Person> femaleMature = filter.check(people);

//事实上,Java8中提供了内建的filter方法
List<Person> femaleMature = people.stream().filter(person -> person.gender == Person.Gender.female)
                                           .filter(person -> person.age >= 18)
                                           .collect(Collectors.toList());

评论区