跳到主要内容

抽象工厂模式 (Abstract Factory)

· 阅读需 4 分钟

抽象工厂模式是一种创建型设计模式,它能创建一系列相关的对象,而无需指定其具体类。

优点:

  1. 你可以确保同一工厂生成的产品相互匹配。
  2. 你可以避免客户端和具体产品代码的耦合。
  3. 单一职责原则。 你可以将产品生成代码抽取到同一位置, 使得代码易于维护。
  4. 开闭原则。 向应用程序中引入新产品变体时, 你无需修改客户端代码。

缺点:

  1. 由于采用该模式需要向应用中引入众多接口和类, 代码可能会比之前更加复杂。

示例

抽象工厂模式的特点是,定义一个抽象工厂类或者接口,由此定义了规范的工厂方法(造桌子、造椅子),将此规范应用于具体的工厂类,例如现代风格工厂和复古风格工厂,生产出不同风格的桌子椅子,他们具有相同的规范,能坐、能放东西。想创建新的风格家具,只需要编写新风格的工厂类并实现抽象工厂接口,不用改动任何已有代码。

抽象工厂,构造方法返回的是抽象的产品。客户端代码,也是与抽象的产品进行交互,无需知道其内部细节,只需要知道椅子可以 sitOn,桌子可以 putSomething

interface AbstractFactory {
createChair(): AbstractChair;
createDesk(): AbstractDesk;
}

interface AbstractChair {
type: string;
sitOn(): void;
}

interface AbstractDesk {
type: string;
putSomething(): void;
mixedFurniture(chair: AbstractChair): void;
}

class MordernChair implements AbstractChair {
type = 'MordernChair';
sitOn() {
console.log('Sitting on Mordern chair.');
}
}

class ClassicChair implements AbstractChair {
type = 'ClassicChair';
sitOn() {
console.log('Sitting on Classic chair.');
}
}

class MordernDesk implements AbstractDesk {
type = 'MordernDesk';
putSomething() {
console.log('Put something on Mordern desk.');
}
mixedFurniture(chair: AbstractChair): void {
console.log(`Combo taste: ${chair.type} ${this.type}`);
}
}
class ClassicDesk implements AbstractDesk {
type = 'ClassicDesk';
putSomething() {
console.log('Put something on Classic desk.');
}
mixedFurniture(chair: AbstractChair): void {
console.log(`Combo taste: ${chair.type} ${this.type}`);
}
}

class MordernStyleFactory implements AbstractFactory {
createChair(): AbstractChair {
return new MordernChair();
}
createDesk(): AbstractDesk {
return new MordernDesk();
}
}

class ClassicStyleFactory implements AbstractFactory {
createChair(): AbstractChair {
return new ClassicChair();
}
createDesk(): AbstractDesk {
return new ClassicDesk();
}
}

// 客户端代码通过抽象类型与工厂和产品进行交互,这使得耦合度很低
function ClientCode(factory: AbstractFactory) {
// 根据传入的工厂,创建对应风格的家具
const chair = factory.createChair();
const desk = factory.createDesk();
chair.sitOn();
// 桌椅混合套餐
desk.mixedFurniture(chair);
}

// 现代风格工厂
ClientCode(new MordernStyleFactory());

工厂方法模式 (Factory Method)

· 阅读需 4 分钟

工厂方法模式是一种创建型设计模式,在父类中提供一个创建对象的方法,允许子类决定实例化对象的类型。

客户端将由不同工厂创建的所有产品都视为抽象的,客户端知道所有产品都提供的交付方法,但是并不关心其具体实现方式。也就是说,产品要有统一的接口,让客户知道怎么用,但不需要知道所调用方法的内部逻辑。如果某一天需要添加新的产品,不需要破坏原有代码,写新的工厂类来创建新产品即可,产品仍然是遵循统一的接口,对客户来说,使用并没有变化。

优点:

  1. 可以避免创建者和具体产品之间的紧密耦合。
  2. 单一职责原则。可以将产品创建代码放在程序的单一位置,从而使得代码更容易维护。
  3. 开闭原则。无需更改现有客户端代码,你就可以在程序中引入新的产品类型。

缺点:

  1. 应用工厂方法模式需要引入许多新的子类,代码可能会因此变得更复杂。

示例

以下代码有两个重点,分别是工厂和产品。

定义了一个 Creator 抽象类,包含了抽象工厂方法,和一些业务代码;具体的 ConcreteCreatorA 和 ConcreteCreatorB 继承该抽象类,实现各自具体的工厂方法用于创建不同产品。

定义了一个 Product 接口,制定了产品的特征;规定产品有一个 operation() 函数;具体的产品 ConcreteProductA 和 ConcreteProductB 实现了该接口,各自的 operation() 内部代码都不相同。

// Product 产品接口,定义了产品具有 operation 特征
// ConcreteProductA ConcreteProductB 实现 Product 接口,是具体的产品实体类。
interface Product {
operation(): string;
}

class ConcreteProductA implements Product {
public operation(): string {
return '[Result of ConcreteProductA]';
}
}

class ConcreteProductB implements Product {
public operation(): string {
return '[Result of ConcreteProductB]';
}
}

/**
* 虽然名字叫创建者,但是它的主要责任并不是创建产品,
* 通常它包含一些依赖于 Product 对象的核心业务逻辑,Product 对象由工厂方法返回。
* 子类可以通过重写工厂方法、返回不同类型的 Product,间接地改变业务逻辑。
*/
abstract class Creator {
public abstract factoryMethod(): Product;

public someOperation() {
const product = this.factoryMethod();
console.log('Do something, ' + product.operation());
}
}

class ConcreteCreatorA extends Creator {
public factoryMethod(): ConcreteProductA {
return new ConcreteProductA();
}
}

class ConcreteCreatorB extends Creator {
public factoryMethod(): ConcreteProductB {
return new ConcreteProductB();
}
}

/**
* 客户端代码,无需了解 Creator 类的内部逻辑,直接无脑调用 someOperation 函数;
* 该函数写在了 Creator 类中,其业务逻辑依赖于工厂方法创建的 Product 对象;
*/
function ClientCode(creator: Creator) {
creator.someOperation();
}

ClientCode(new ConcreteCreatorA());
ClientCode(new ConcreteCreatorB());

输出结果:

Do something, [Result of ConcreteProductA]
Do something, [Result of ConcreteProductB]

单例模式 (Singleton Pattern)

· 阅读需 1 分钟

单例模式提供方法,返回相同的缓存实例对象。

定义一个 Singleton 类,它应该具备以下特征:

  • 实例对象私有,不可被直接访问
  • 实现静态公有的 getInstance() 方法获取实例对象
  • 类不可使用 new 来实例化

示例

单例模式示例代码如下:

class Singleton {
private static instance: Singleton;

// 私有构造函数,不可用 new 实例化
private constructor() { }

public static getInstance(): Singleton {
if (!this.instance) {
this.instance = new Singleton();
}

return Singleton.instance;
}

// 单例也有一些业务逻辑,在实例上执行
public printHello() {
console.log('Hello');
}
}

const s1 = Singleton.getInstance();
const s2 = Singleton.getInstance();
s1.printHello(); // Hello
s1 === s2; // true

可以出 s1s2 引用的是同一个实例对象。

TS 中 Declare 的作用

· 阅读需 5 分钟

在很多项目里可以看到 *.d.ts 这样的文件,在这里面有时会有很多 declare 出现。它的作用是,声明一个模块、变量、类型...

有时候,一些库不提供类型声明,使用它的时候就会很迷茫,没有代码提示。有时候,使用某个SDK,如果不去声明一下它,会报错说 “找不到名称xxx” ,这时候就需要我们自己去声明一下它。

可以直接在使用它的ts文件里声明,也可以在 *.d.ts 里进行全局范围的声明。

声明名称空间(模块)

某个公司实现了一个库,叫 BOS3D,项目中要使用这个库,且已经把他挂到了 window 对象上,我们在项目里可以直接使用 BOS3D.Version 或者 new BOS3D.Viewer(),但是在TS写的项目中,会提示找不到名称BOS3D,这时候需要自己简要地声明一下这个东西。

declare namespace BOS3D {
var MODE: 'prod' | 'dev' | 'test';
const Version = 4;

class Viewer {
constructor();
}
}

new BOS3D.Viewer(); // 可以使用了
BOS3D.Version; // 4

经过简单的声明之后,就不报错了,也有简单的提示了。

声明变量

使用 declare 来声明变量,可以用 var, const, let 关键字来完成声明。如果想要定义只读的变量,就用 const。如果想要变量是块级作用域,就用 let。

declare var age: number;
declare const year: number;
declare let month: number;

声明带属性的对象

假如有一个 myLib 全局变量,里面有一个 makeGreeting 函数,和一个 numberOfGreetings 属性来储存问候次数。应该这样来定义声明文件。

declare namespace myLib {
function makeGreeting(s: string): string;
let numberOfGreetings: number;
}

重载函数签名

这种场景,在编译器不知道函数定义在哪里时,告诉编译器该函数是怎样定义的类型签名,而不是在自己实现函数签名重载时使用。

正确用法:

// 使用了某些未知库,找不到函数定义,没有代码提示;这样来自己定义一下,方便开发;
declare function getPerson(s: string): object;
declare function getPerson(n: number): object;

错误用法:

declare function getPerson(s: string): object;
declare function getPerson(n: number): object;
function getPerson(arg: string | number): object {
// ...
}

如果自己实现函数签名重载,直接 function 连串下来。不用 declare,它的作用只是告诉编译器东西是怎么定义的,和实现毫无关系。

声明类

使用 declare class 来描述一个类,或者一个 似类 的对象,类可以定义属性或者方法,也包括构造器。

declare class Greeter {
constructor(greeting: string);
greeting: string;
showGreeting(): void;
}

const myGreeter = new Greeter("hello, world");
myGreeter.greeting = "howdy";
myGreeter.showGreeting();

class SpecialGreeter extends Greeter {
constructor() {
super("Very special greetings");
}
}

这是定义类型!!不是实现,运行肯定会报错!只是方便开发过程中有代码提示。

组织类型

用名称空间 namespace 把类型给组织起来,更规范管理类型。

declare namespace GreetingLib {
interface LogOptions {
verbose?: boolean;
}
interface AlertOptions {
modal: boolean;
title?: string;
color?: string;
}
}

// 可以嵌套定义名称空间,也可以这样通过 xxx.xx 定义内嵌的名称空间
declare namespace GreetingLib.Options {
interface Log {
verbose?: boolean;
}
interface Alert {
modal: boolean;
title?: string;
color?: string;
}
}

function exportLog(arg: GreetingLib.Options.Log) {
//...
}
标签:

TS 中 module 和 namespace

· 阅读需 2 分钟

模块和名称空间,实际用起来没什么区别,但是概念上有些不同。

模块的设计理念是分割和组织代码文件,一个文件就是一个module。在一个文件中访问另一个文件必须要加载(import或者require)它。

引用TS官网的一句话:

It’s important to note that in TypeScript 1.5, the nomenclature has changed. “Internal modules” are now “namespaces”. “External modules” are now simply “modules”

自 TS 1.5 以后,namespace 可以看作是内部模块,modules 就是它原本的意思(外部模块)。命名空间顾名思义,提供了一个空间,在这里面定义的变量、接口、函数等,与其他空间隔离,也就是说他们的名称相同也不会互相影响,就像 Java 中的包。

示例

/// <reference path = "..." />,这个指令会引入要加载的ts文件,然后就可以使用其中的内容。

下面这些代码,在不同文件里共同定义了一个名称空间 Card,该名称空间下暴露了几个类,分别是 Circle, Triangle, Square。并在名称空间之外,定义了一个 Drawable 接口。

/// <reference path = "./Shape.ts" />

// Circle.ts
namespace Card {
export class Circle implements Drawable {
type = 'Circle';
constructor(){

}
draw() {
console.log('Draw Circle.');
}
}
}
// Shape.ts
interface Drawable {
draw(); // 定义抽象 draw 方法
}

namespace Card {
export class Triangle implements Drawable {
// 实现 draw 方法
draw() {
console.log('Draw Triangle.');
}
}
export class Square implements Drawable {
draw() {
console.log('Draw Square.');
}
}
}
/// <reference path="./Circle.ts" />
/// <reference path="./Shape.ts" />

// Test.ts
function testCircle(shape: Card.Circle) {
shape.draw();
}

function testTriangle(shape: Card.Triangle) {
shape.draw();
}

function testSquare(shape: Card.Square) {
shape.draw();
}

testCircle(new Card.Circle());
标签: