流行设计模式:依赖注入、工厂、单例、观察者和策略模式 - bognov


本文目的是提供这几种模式的简化示例。
可以在我的Github上找到[url=https://github.com/bgdnvk/patterns]此存储库[/url]中的所有代码。

策略模式
让您在运行时选择算法。名称是“策略”,因为在给定特定上下文的情况下,您可以选择适合该上下文的策略。

这与 SOLID 原则之一直接相关,开闭原则指出软件实体(类、模块、函数等)应该对扩展开放,但对修改关闭。

让我们看一个使用 TypeScript 的示例,如果您使用任何其他语言,最重要的部分是您了解类和接口如何协同工作。

//define interface we will use for different strategies
interface Strategy {
    execute(str: string): string
}

//define different strategies we could use
class upperCaseStrategy implements Strategy {

    public execute(str: string): string {
        return str.toUpperCase()
    }
}

class lowerCaseStrategy implements Strategy {

    public execute(str: string): string {
        return str.toLowerCase()
    }
}

class Context {

   
//we will be using a different strategy for every situation
   
//however here we just define a parameter of type Strategy
   
//that will be changed as we wish
    private strategy: Strategy

   
//while making the object we will include the strategy to use
    constructor(strategy: Strategy) {
        this.strategy = strategy
    }

   
//in case we want to change out strategy we have a method for that
    public setStrategy(strategy: Strategy) {
        this.strategy = strategy
    }

   
//we will get back the modified string according to the strategy we use
    public executeStrategy(str: string): string{
        return this.strategy.execute(str)
    }
}


//define a string we will be using for testing
const randomString = 'rAnDoM sTrInG'
console.log('inittial string:', randomString)
//make a new context with the upper case strategy
const context = new Context(new upperCaseStrategy())
//use the current strategy (which is upperCase) to get an upper case String
const upperCaseString = context.executeStrategy(randomString)
//check it's upper case
console.log('upper case:', upperCaseString)
//change context's strategy
context.setStrategy(new lowerCaseStrategy())
//get the lower case string with a different strategy
const lowerCaseString = context.executeStrategy(upperCaseString)
//check it's lower case
console.log('lower case:', lowerCaseString)

在此示例中,您可以看到我们将如何在整个程序中使用相同的 Context 类,但通过更改策略,我们将能够在调用 executeStrategy 方法时获得不同的结果。

如果你想要更复杂的例子 Refactoring Guru在 Go 中有一个缓存实现,[url=https://refactoring.guru/design-patterns/strategy/java/example]在 Java 中[/url]有一个支付方法。

观察者
观察者模式依赖于一个原则:可观察者(通常称为主题或事件管理器)需要通知/更新它包含的关于事件的观察者(可以称为订阅者)。

最常见的情况是某种通知,你想通知你的用户(或一组特定的用户)已经发生的事情,所以你需要观察者并推送他们的更新方法。

请注意,要通知观察者,他们需要以某种方式连接到可观察对象,并且可观察对象还需要有一种方法来调用可观察对象的通知/更新方法。

让我们看一下代码,但请记住,我交替使用可观察、主题和事件管理器/调度程序这三个词。观察者和订阅者也是如此。

//subject or event manager/dispatcher
interface Observable {
   
//add the observers we want to track out observable
    add(observer: Observer): void
   
//same but delete
    remove(observer: Observer): void
   
//notify all of the observers
    notify(): void
}

//event listener
interface Observer {

    name: string
   
//the update method will be triggered when the observable uses it
   
//you can pass the observable to get more context or not
    update(observable: Observable): void
}

//since a lot of the examples use Subject instead of Observable let's call our class Subject
//it will be the one notifying the different observers
class Subject implements Observable {

    name: string

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

   
//using a Map but this can just be an array
    private observersMap: Map<Observer['name'], Observer> = new Map<Observer['name'], Observer>

   
//add the observer/subscriber to our observable/subject
    public add(observer: Observer): void {

        this.observersMap.set(observer.name, observer)
        console.log(`added observer ${observer.name} to ${this.name}`)
    }

    public remove(observer: Observer): void {

        this.observersMap.delete(observer.name)
        console.log(`removed observer ${observer.name} to ${this.name}`)
    }

   
//we do a forEach on our map so we can press the update method in all of them and pass the current observable/subject
    public notify(): void {

       
//note that we allow to pass the instance of our observable to every observer, check the interface
        this.observersMap.forEach((v,k,m) => v.update(this)) 
    }

    public getName(): string {
        return this.name
    }

   
//example of a possible method will have the notify method inside, note how we call it at the end to notify our observers
   
//this is a simple change of name but it could be anything
    public changeName(name: string): void {

        console.log(`changing name from ${this.name} to ${name}..`)
        this.name = name

        console.log(`notifications being sent..`)
        this.notify()
    }
}

class webObserver implements Observer {

    name: string

    constructor(name: string) {
        this.name = name
    }
    update(observable: Subject): void {
        console.log(`${this.name} notification from ${observable.getName()}`)
    }
}

class phoneObserver implements Observer {

    name: string

    constructor(name: string) {
        this.name = name
    }
    update(observable: Subject): void {
        console.log(`${this.name} notification from ${observable.getName()}`)
    }
}

//init all our objects
const subject = new Subject('THANOS SERVICE')
const webObs = new webObserver('WEB')
const phoneObs = new phoneObserver('PHONE')

//make sure to subscribe/add the observers the observable
subject.add(webObs)
subject.add(phoneObs)

//notify all of our observers
subject.notify()

//now let's change the name
//this is just an example of a possible change of state inside the class, it could be a new item arrival for example or something else
subject.changeName('IRONMAN')

这里是GoJava


观察者与发布/订阅
您可能已经注意到,观察者模式最终可能会过于紧密耦合,并且它与另一种架构非常相似:pub/sub。

Pub/Sub 有点不同并且增加了复杂性,但是如果您决定使用事件,那么它会很棒,因为它为订阅者提供了一个单独的渠道。

单例
如果您想确保该类只有一个实例,请使用单例模式。尽管有些人可能会反对,但这种模式被广泛使用,尤其是在数据库连接方面。
这种设计的美妙之处在于它使用了私有初始化器。

class Singleton {
    //here's the best part, we use a static variable to store our singleton instance
   
//and make it private so we can only get it through the getsingleton() method
    private static singleton: Singleton

    private constructor() {
        console.log('starting singleton..')
    }

   
//call this method every time we want a singleton
    public static getSingleton(): Singleton {

        console.log('getting singleton..')
       
//if the singleton instance (static singleton) is null we will initialize it
        if(!Singleton.singleton) {
            console.log('no singleton available')
            Singleton.singleton = new Singleton()
        }

        return Singleton.singleton
    }
}

function mainProgram() {
   
//... code
    const singleton1 = Singleton.getSingleton()
    const singleton2 = Singleton.getSingleton()

    if(singleton1 === singleton2) {
        console.log('same instance')
    }

}
mainProgram()

JavaGo中也有示例

工厂
工厂模式的全部要点是有一个特定的位置(工厂接口),您可以在其中创建所需的对象。工厂称为“创建者”,创建的对象称为“产品”。
基本上我们所做的是封装业务逻辑(一种构造对象的特定方式)。原理非常简单,但您可以根据需要将其复杂化,其理念是始终有一个可用的工厂方法,您可以在代码库中移动并获取您想要或需要的具体对象。
我真的很喜欢Refactoring Guru的这个定义:

工厂方法是一种创建型设计模式,它提供用于在超类中创建对象的接口,但允许子类更改将要创建的对象的类型。

//pizza is going to be the product that a factory can make
interface Pizza {
    name: string
    getName(): string
    cookingTime(): void
    eat(): void
}

//this is just an example, NY pizza is different from Chicago pizza
class NewYorkPizza implements Pizza {

    name: string

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

    getName(): string {
        return this.name
    }

    cookingTime(): void{
       console.log('15min') 
    }

    eat(): void {
        console.log('eating NY pizza')
    }
}

class ChicagoPizza implements Pizza {

    name: string

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

    getName(): string {
        return this.name
    }
    cookingTime(): void {
        console.log('30min')
    }
    eat(): void {
        console.log('eating Chicago pizza')
    }
}

//this is our creator, we can use this interfece anywhere in order to make our products(pizzas)
interface PizzaFactory {
    makePizza(pizza: Pizza): Pizza
}

//lets think of different restaurants that want to make pizzas
class NewYorkRestaurant implements PizzaFactory {

    makePizza(): Pizza {
        return new NewYorkPizza('best NY pizza')
    }
}

class ChicagoRestaurant implements PizzaFactory {

    makePizza(): Pizza {
        return new ChicagoPizza('best Chicago pizza')
    }
}

//create our factories
const newYorkRestaurant: NewYorkRestaurant = new NewYorkRestaurant()
const chicagoRestaurant: ChicagoRestaurant = new ChicagoRestaurant() 

//get the product
const myNewYorkPizza = newYorkRestaurant.makePizza()
const myChicagoPizza = chicagoRestaurant.makePizza()

//here we use our products
//this is a simple example but remember you can add a lot more logic and extend the examples
//the point of the pattern is so that you could use the factories anywhere you need
//and modify the logic inside
//try adding a delivery method for example to one of the restaurants or something concrete to the pizzas
const newYorkPizzaName = myNewYorkPizza.getName()
console.log(`NY pizza name is: ${newYorkPizzaName}`)
myNewYorkPizza.cookingTime()

const chicagoPizzaName = myChicagoPizza.getName()
console.log(`NY pizza name is: ${chicagoPizzaName}`)
myChicagoPizza.cookingTime()

JavaGo

抽象工厂
通常使用Factory方法,或者好吧,它演化成了一个Abstract Factory。顾名思义,你所做的就是抽象工厂,这样你就可以创建更多具体的工厂来混合具体产品的不同实现。
Refactoring Guru使用了一个 GUI 示例,我认为这是该模式的最佳示例

他们还有伪代码来说明正在发生的一切。这个来自维基百科的Python 示例也很不错。

依赖注入
DI 是那些太容易理解或太难理解的概念之一,我的意思是即使经常使用它的人也可能无法完全解释正在发生的事情或它是如何工作的循环。
我的目的是让您有一个大致的了解,以便您可以进一步深入,或者至少能够在您的项目中使用 DI。

依赖注入依赖于控制反转原则。