案例分析:设计模式与代码的结构特性

Posted demonatic

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了案例分析:设计模式与代码的结构特性相关的知识,希望对你有一定的参考价值。

    软件设计模式(Design pattern),又称设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的重用性。

  本文主要介绍设计模式中的策略模式,将对其目的、结构进行分析,包括各个模块的内聚度和模块之间的耦合度,并提供C++范例,说明封装方法。

  一、策略模式目的

    定义一系列算法,把它们一个个封装起来,并且时它们可以相互替换,使得算法可以独立于使用它们的客户端而变化。

  二、为什么要使用策略模式?

     试想一个程序需要支持不同算法来分析一个文本流;如果程序直接包含分析文本算法的代码,那么当需要支持多种分析算法的时候程序将变得庞大且难以维护,算法和使用算法的代码高度耦合在一起,增加新的算法或改变现有算法将十分困难。

    那么如果定义一个算法接口类,不同算法作为不同的子类继承这个接口呢?这也会导致一些问题,如动态改变算法会比较困难,最后你得到了一堆相关的类,它们之间唯一的差别是所使用的算法或者行为。

  三、策略模式结构

    这个时候策略模式便应运而生了。我们定义一个抽象的Strategy类包含一个Algorithm接口,使用子类ConcreteStrategyA/B/C来支持具体策略;Context类由一个Strategy对象配置,并维护对它的引用。整个结构如下:

    技术图片

 

    通常将Strategy实现为无状态的对象,其余的状态都由Context来维护,Context在每一次对Strategy对象的请求中都将这个状态传递过去,Strategy对象不应该在各次调用之间维护状态。

    具体实现上需要考虑如何让Context传递给Strategy所需的状态。

    方法一,可以让Context将数据放在参数中传递给Strategy操作,这使得Strategy和Context解耦,但另一方面Context可能发送一些Strategy不需要的数据。

    方法二,让Context自身作为一个参数传递给Strategy,该Strategy再显示地调用Context方法请求数据,或者Strategy可以存储它对Context的一个引用,这样根本不需要在调用时传递任何东西。但现在Context必须对它的数据定义一个更为精细的接口,这将Strategy和Context紧密耦合在一起。

    方法三,将Strategy作为Context的模板参数。但这种方式仅仅当满足如下条件时才可以用:①可以在编译时选择Strategy;②不需要在运行时改变。    

 

         

template<class Astrategy>
class Context{
public:
    void operation(){ 
        theStrategy.DoAlgorithm();
    }
private:
    Astrategy theStrategy;
};

class MyStrategy{
public:
    void DoAlgorithm();
};

Context<MyStrategy> aContext;

 

    使用模板不需要给Strategy定义接口的抽象类。把Strategy作为一个模板参数也使得可以将一个Strategy和它的Context静态绑定在一起,从而提高效率。

 

   四、示例分析

    假设我们有一个APP,用户登录时支持多种方式验证用户身份,包含Basic、Digest、OpenID、OAuth,我们首先想到可以用如下方式验证代码

class BasicAuth {}
class DigestAuth {}
class OpenIDAuth {}
class OAuth {}

class AuthProgram {
    runProgram(authStrategy:any, ...) {
        this.authenticate(authStrategy)
        // ...
    }
    authenticate(authStrategy:any) {
        switch(authStrategy) {
            if(authStrategy == "basic")
                useBasic()
            if(authStrategy == "digest")
                useDigest()
            if(authStrategy == "openid")
                useOpenID()
            if(authStrategy == "oauth")
                useOAuth()
        }
    }
}

  这种方式需要让AuthProgram以来一长串switch-case,并且当我们只需要使用一种验证方式时,也得进行一长串判断。而如果我们使用Strategy Pattern,可以给所有验证方式定义公共接口:

interface AuthStrategy {
    auth(): void;
}
class Auth0 implements AuthStrategy {
    auth() {
        log(Authenticating using Auth0 Strategy)
    }
}
class Basic implements AuthStrategy {
    auth() {
        log(Authenticating using Basic Strategy)
    }
}
class OpenID implements AuthStrategy {
    auth() {
        log(Authenticating using OpenID Strategy)
    }
}

  而AuthenProgram可以简化许多:

class AuthProgram {
    private _strategy: AuthStrategy
    use(strategy: AuthStrategy) {
        this._strategy = strategy
        return this
    }
    authenticate() {
        if(this._strategy == null) {
            log("No Authentication Strategy set.")
        }
        this._strategy.auth()
    }
    route(path: string, strategy: AuthStrategy) {
        this._strategy = strategy
        this.authenticate()
        return this
    }
}

  调用Authen:

 // Authenticating using OpenID Strategy
new AuthProgram().use(new OpenID()).authenticate()

  可以看到现在的AuthenProgram没有了一长串条件判断语句,我们只需要为其设置好Strategy,让AuthenProgram直接调用即可,实现AuthenProgram与具体的验证算法实现方式解耦。需要什么方式进行验证,只需要传入验证Strategy给验证程序即可。

  

    

   

    

以上是关于案例分析:设计模式与代码的结构特性的主要内容,如果未能解决你的问题,请参考以下文章

案例分析:设计模式与代码的结构特性

案例分析:设计模式与代码的结构特性

案例分析:设计模式与代码的结构特性

案例分析:设计模式与代码的结构特性

案例分析:设计模式与代码的结构特性

案例分析:设计模式与代码的结构特性