数据结构模式¶
概念¶
定义¶
- 常常有一些组件在内部具有特定的数据结构,如果让客户程序依赖这些特定的数据结构,将极大地破坏组件的复用。
- 这时候,将这些特定数据结构封装在内部,在外部提供统一的接口,来实现与特定数据结构无关的访问,是一种行之有效的解决方案。
组合模式(Composite)¶
动机¶
- 软件在某些情况下,客户代码过多地依赖于对象容器复杂的内部实现结构,对象容器内部实现结构(而非抽象接口)的变化将引起客户代码的频繁变化,带来了代码的维护性、拓展性等弊端。
- 如何将“客户代码与复杂的对象容器结构”解耦?让对象容器自己来实现自身的复杂结构,从而使得客户代码就像处理简单对象一样来处理复杂的对象容器?
模式定义¶
- 将对象组合成树形结构以表示“部分-整体”的层次结构。
- Composite 使得用户对单个对象和组合对象的使用具有一致性(稳定)。
UML 类图¶
角色¶
-
抽象构件(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 类图¶
角色¶
- 抽象聚合(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 类图¶
角色¶
- 抽象处理者(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 类图¶
角色¶
- 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));
}
}