Skip to content

数据结构模式

概念

定义

  • 常常有一些组件在内部具有特定的数据结构,如果让客户程序依赖这些特定的数据结构,将极大地破坏组件的复用。
  • 这时候,将这些特定数据结构封装在内部,在外部提供统一的接口,来实现与特定数据结构无关的访问,是一种行之有效的解决方案。

组合模式(Composite)

动机

  • 软件在某些情况下,客户代码过多地依赖于对象容器复杂的内部实现结构,对象容器内部实现结构(而非抽象接口)的变化将引起客户代码的频繁变化,带来了代码的维护性、拓展性等弊端。
  • 如何将“客户代码与复杂的对象容器结构”解耦?让对象容器自己来实现自身的复杂结构,从而使得客户代码就像处理简单对象一样来处理复杂的对象容器?

模式定义

  • 将对象组合成树形结构以表示“部分-整体”的层次结构。
  • Composite 使得用户对单个对象和组合对象的使用具有一致性(稳定)。

UML 类图

x

角色

  • 抽象构件(Component)角色

    • 为树叶构件和树枝构件声明公共接口,并实现它们的默认行为。
  • 树叶构件(Leaf)角色:

    • 组合中的叶节点对象,它没有子节点,用于继承或实现抽象构件。
  • 树枝构件(Composite)角色 / 中间构件:

    • 组合中的分支节点对象,它有子节点,用于继承和实现抽象构件。它的主要作用是存储和管理子部件,通常包含 Add()、Remove()、GetChild() 等方法。

代码

// 抽象构件
interface Component {
  add(c: Component): void;

  remove(c: Component): void;

  getChild(i: number): Component;

  operation(): void;
}

// 树叶构件
class Leaf implements Component {
  private name: string;

  constructor(name: string) {
    this.name = name;
  }

  public add(c: Component): void {}

  public remove(c: Component): void {}

  public getChild(i: number): Component {
    return null;
  }

  public operation(): void {
    console.log('树叶' + name + ':被访问!');
  }
}

// 树枝构件
class Composite implements Component {
  private children: Array<Component> = new Array<Component>();

  public add(c: Component): void {
    this.children.push(c);
  }

  public remove(c: Component): void {
    const index = this.children.indexOf(c);
    this.children.splice(index, 1);
  }

  public getChild(i: number): Component {
    return this.children[i];
  }

  public operation(): void {
    for (const obj of this.children) {
      obj.operation();
    }
  }
}

export default class CompositePattern {
  public static main(args: Array<string>): void {
    const c0: Component = new Composite();
    const c1: Component = new Composite();
    const leaf1: Leaf = new Leaf('1');
    const leaf2: Leaf = new Leaf('2');
    const leaf3: Leaf = new Leaf('3');
    c0.add(leaf1);
    c0.add(c1);
    c1.add(leaf2);
    c1.add(leaf3);
    c0.operation();
  }
}

要点总结

  • Composite 模式采用树形结构来实现普遍存在的对象容器,从而将“一对多”的关系转化为“一对一”的关系,使得客户代码可以一致地(复用)处理对象和对象容器,无需关心处理的是单个的对象,还是组合的对象容器。
  • 将“客户代码与复杂的对象容器结构”解耦的是Composite 的核心思想;解耦之后,客户代码将与纯粹的抽象接口——而非对象容器的内部实现结构——发生依赖,从而更能“应对变化”。
  • Composite模式在具体实现中,可以让父对象中的字对象反向追溯;如果父对象有频繁的遍历需求,可以使用缓存技巧来改善效率。

迭代器模式(Iterator)

动机

  • 在软件构建过程中,集合对象内部结构常常变化各异。但对于这些集合对象,我们希望在不暴露其内部结构的同时,可以让外部客户代码透明地访问其中包含的元素;同时这种“透明遍历”也为“同一种算法在多种集合对象上进行操作”提供了可能。
  • 使用面向对象技术将这种遍历机制抽象为“迭代器对象”为“应对变化中的集合对象”提供了一种优雅的方式。

模式定义

  • 提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露(稳定)该对象的内部表示。
  • 访问一个聚合对象的内容而无须暴露它的内部表示;遍历任务交由迭代器(Iterator)完成,这简化了聚合类。

UML 类图

x

角色

  • 抽象聚合(Aggregate)角色:定义存储、添加、删除聚合对象以及创建迭代器对象的接口。
  • 具体聚合(ConcreteAggregate)角色:实现抽象聚合类,返回一个具体迭代器的实例。
  • 抽象迭代器(Iterator)角色:定义访问和遍历聚合元素的接口,通常包含 hasNext()、first()、next() 等方法。
  • 具体迭代器(Concretelterator)角色:实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置。

代码

// 抽象聚合
interface Aggregate<T> {
  add(obj: T): void;

  remove(obj: T): void;

  getIterator(): Iterator<T>;
}

// 具体聚合
class ConcreteAggregate<T> implements Aggregate<T> {
  private list: Array<T> = new Array<T>();

  public add(obj: T): void {
    this.list.push(obj);
  }

  public remove(obj: T): void {
    const index = this.list.indexOf(obj);
    if (index > -1) {
      this.list.splice(index, 1);
    }
  }

  public getIterator(): Iterator<T> {
    return new ConcreteIterator(this.list);
  }
}

// 抽象迭代器
interface Iterator<T> {
  first(): T;

  next(): T;

  hasNext(): boolean;
}

// 具体迭代器
class ConcreteIterator<T> implements Iterator<T> {
  private list: Array<T> = null;
  private index = -1;

  constructor(list: Array<T>) {
    this.list = list;
  }

  public hasNext(): boolean {
    if (this.index < this.list.length - 1) {
      return true;
    } else {
      return false;
    }
  }

  public first(): T {
    this.index = 0;
    const obj: T = this.list[this.index];
    return obj;
  }

  public next(): T {
    let obj: T = null;

    if (this.hasNext()) {
      obj = this.list[++this.index];
    }

    return obj;
  }
}

export default class IteratorPattern {
  public static main(args: Array<string>): void {
    const ag: Aggregate<string> = new ConcreteAggregate();
    ag.add('中山大学');
    ag.add('清华大学');
    ag.add('北京邮电大学');

    console.log('聚合的内容有:');
    const it: Iterator<string> = ag.getIterator();
    while (it.hasNext()) {
      const ob: string = it.next();
      console.log(ob.toString() + '\t');
    }

    const ob: string = it.first();
    console.log('\nFirst:' + ob.toString());
  }
}

要点总结

  • 迭代抽象:访问一个聚合对象的内容而无需暴露它的内部表示。
  • 迭代多态:为遍历不同的集合结构提供了一个统一的接口,从而支持同样的算法在不同的集合结构上进行操作。
  • 迭代器的健壮性考虑:遍历的同时更改迭代器所在的集合结构,会导致问题。

职责链模式(Chain of Responsibility)

动机

  • 在软件构建过程中,一个请求可能被多个对象处理,但是每个请求在运行时只能有一个接受者,如果显示指定,将必不可少地带来请求发送者与接受者的紧耦合。
  • 如何使请求的发送者不需要指定具体的接受者?让请求的接受者自己在 运行时 决定来处理请求,从而使两者解耦合。

模式定义

  • 使多个对象都有机会处理请求,从而避免请求的发送者与接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递请求,直到有一个对象处理它为止。

UML 类图

x

角色

  • 抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接。
  • 具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。
  • 客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。

代码

// 抽象处理者角色
abstract class Handler {
  private next: Handler;

  public setNext(next: Handler): void {
    this.next = next;
  }

  public getNext(): Handler {
    return this.next;
  }

  // 处理请求的方法
  public abstract handleRequest(request: string): void;
}

// 具体处理者角色1
class ConcreteHandler1 extends Handler {
  public handleRequest(request: string): void {
    console.log('ConcreteHandler1');
    if (request === 'one') {
      console.log('具体处理者1负责处理该请求!');
    } else {
      if (this.getNext() != null) {
        this.getNext().handleRequest(request);
      } else {
        console.log('没有人处理该请求!');
      }
    }
  }
}

// 具体处理者角色2
class ConcreteHandler2 extends Handler {
  public handleRequest(request: string): void {
    console.log('ConcreteHandler2');
    if (request === 'two') {
      console.log('具体处理者2负责处理该请求!');
    } else {
      if (this.getNext() != null) {
        this.getNext().handleRequest(request);
      } else {
        console.log('没有人处理该请求!');
      }
    }
  }
}

export default class ChainOfResponsibilityPattern {
  public static main(args: string[]): void {
    // 组装责任链
    const handler1: Handler = new ConcreteHandler1();
    const handler2: Handler = new ConcreteHandler2();
    handler1.setNext(handler2);

    // 提交请求
    handler1.handleRequest('two');
  }
}

要点总结

  • Chain of Resposibility 模式的应用场合在于“一个请求可能有多个接受者,但是最后真正的接受者只有一个”,这时候请求发送者与接受者的耦合关系有可能出现“变化脆弱”的症状,职责链的目的就是将二者解耦从而更好地应对变化。
  • 应用了 Chain of Resposibility 模式后,对象的职责分派将更具灵活性,我们可以在运行时动态添加/修改请求的处理职责。
  • 如果请求传递到职责链的末尾仍得不到处理,应该有一个合理的缺省机制。这也是每一个接受对象的责任,而不是发出请求的对象的责任。

过滤器模式(Filter)

模式定义

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

UML 类图

x

角色

  • AbstractFilter(抽象过滤器角色):在客户端可以调用它的方法,在抽象过滤器角色中可以知道相关的(一个或者多个)子系统的功能和责任;在正常情况下,它将所有从客户端发来的请求委派到相应的实现类去,传递给相应的实现类对象处理。
  • ConcreteFilter(具体滤器角色):在客户端可以调用它的方法,在具体滤器角色会对目标对象集合进行逻辑过滤,最后再返回一个过滤后的集合。
  • Subject(被过滤角色):在软件系统中可以有一个或者多个目标角色,在具体过滤器中会对目标角色进行逻辑处理以查看是否是我想要的。

代码

// Subject(被过滤角色)
class Person {
  private name: string;
  public gender: string;
  public maritalStatus: string;

  public constructor(name: string, gender: string, maritalStatus: string) {
    this.name = name;
    this.gender = gender;
    this.maritalStatus = maritalStatus;
  }
}

// AbstractFilter(抽象过滤器角色)
interface Criteria {
  meetCriteria(persons: Array<Person>): Array<Person>;
}

// ConcreteFilter(具体滤器角色)
class CriteriaMale implements Criteria {
  public meetCriteria(persons: Array<Person>): Array<Person> {
    const malePersons: Array<Person> = new Array<Person>();
    for (const person of persons) {
      if (person.gender === 'Male') {
        malePersons.push(person);
      }
    }
    return malePersons;
  }
}

// ConcreteFilter(具体滤器角色)
class CriteriaFemale implements Criteria {
  public meetCriteria(persons: Array<Person>): Array<Person> {
    const femalePersons: Array<Person> = new Array<Person>();
    for (const person of persons) {
      if (person.gender === 'Female') {
        femalePersons.push(person);
      }
    }
    return femalePersons;
  }
}

// ConcreteFilter(具体滤器角色)
class CriteriaSingle implements Criteria {
  public meetCriteria(persons: Array<Person>): Array<Person> {
    const singlePersons: Array<Person> = new Array<Person>();
    for (const person of persons) {
      if (person.maritalStatus === 'Single') {
        singlePersons.push(person);
      }
    }
    return singlePersons;
  }
}

// ConcreteFilter(具体滤器角色)
class AndCriteria implements Criteria {
  private criteria: Criteria;
  private otherCriteria: Criteria;

  public constructor(criteria: Criteria, otherCriteria: Criteria) {
    this.criteria = criteria;
    this.otherCriteria = otherCriteria;
  }

  public meetCriteria(persons: Array<Person>): Array<Person> {
    const firstCriteriaPersons: Array<Person> =
      this.criteria.meetCriteria(persons);
    return this.otherCriteria.meetCriteria(firstCriteriaPersons);
  }
}

// ConcreteFilter(具体滤器角色)
class OrCriteria implements Criteria {
  private criteria: Criteria;
  private otherCriteria: Criteria;

  public constructor(criteria: Criteria, otherCriteria: Criteria) {
    this.criteria = criteria;
    this.otherCriteria = otherCriteria;
  }

  public meetCriteria(persons: Array<Person>): Array<Person> {
    const firstCriteriaItems: Array<Person> =
      this.criteria.meetCriteria(persons);
    const otherCriteriaItems: Array<Person> =
      this.otherCriteria.meetCriteria(persons);

    for (const person of otherCriteriaItems) {
      if (firstCriteriaItems.indexOf(person) < 0) {
        firstCriteriaItems.push(person);
      }
    }
    return firstCriteriaItems;
  }
}

export default class CriteriaPatternDemo {
  public static main(args: string[]): void {
    const persons: Array<Person> = new Array<Person>();

    persons.push(new Person('Robert', 'Male', 'Single'));
    persons.push(new Person('John', 'Male', 'Married'));
    persons.push(new Person('Laura', 'Female', 'Married'));
    persons.push(new Person('Diana', 'Female', 'Single'));
    persons.push(new Person('Mike', 'Male', 'Single'));
    persons.push(new Person('Bobby', 'Male', 'Single'));

    const male: Criteria = new CriteriaMale();
    const female: Criteria = new CriteriaFemale();
    const single: Criteria = new CriteriaSingle();
    const singleMale: Criteria = new AndCriteria(single, male);
    const singleOrFemale: Criteria = new OrCriteria(single, female);

    console.log('Males: ');
    console.log(male.meetCriteria(persons));

    console.log('\nFemales: ');
    console.log(female.meetCriteria(persons));

    console.log('\nSingle Males: ');
    console.log(singleMale.meetCriteria(persons));

    console.log('\nSingle Or Females: ');
    console.log(singleOrFemale.meetCriteria(persons));
  }
}