您的位置:首页 > 其它

设计模式的一点总结和思考(一)创建型

2015-05-18 12:42 323 查看

面向接口编程

对于当前不知道或无法确定的东西,我们就抽象它,只对其接口操作,即现在不知道具体的涉及对象,但我知道如何使用它,先用其接口,待以后知道了具体的对象之后,再绑定上即可,这就是所谓的封装变化。

虽然不确定目标是谁,但可以确定如何使用目标。

多种多样的设计模式其实做的就是 封装变化 ,面对不同的情景,分析什么是变化的,什么是不变的,封装变化,使上层代码能够“以不变应万变”。

简单工厂

【严格来说其并不算是一种设计模式,其就是把一堆判断创建的语句放在了一个函数中,通过传入的参数决定创建哪一个产品的实例】

在简单工厂模式中,可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。(这也是其优点:把实现对象的创建和对象的使用分离)

在实际开发中,还可以在调用时将所传入的参数保存在XML等格式的配置文件中,修改参数时无须修改任何源代码。

工厂类将全部创建逻辑集中到了一个工厂类中;它所能创建的类只能是事先考虑到的,如果需要添加新的类,则就需要改变工厂类了。

class Factory
{
public:
    static Product* CreateProduct(int n )
    {
        switch(n)
        {
        case 1:
            return new FirstProduct;
            break;
        case 2:
            return new SecondProduct;
            break;
        }
    }
};


【适用环境】

工厂类负责创建的对象比较少:由于创建的对象较少,不会造成工厂方法中的业务逻辑太过复杂。

客户端只知道传入工厂类的参数,对于如何创建对象不关心:客户端既不需要关心创建细节,甚至连类名都不需要记住,只需要知道类型所对应的参数。

由于简单工厂很容易违反高内聚责任分配原则,因此一般只在很简单的情况下应用。

工厂方法

【产品主体延迟到子类决定,产品的操作由基类实现,子类只负责具体创建某产品】

(变化的是产品,不变的是对产品的操作)

【动机】

由于简单工厂模式的局限性,比如:工厂现在能生产ProductA、ProductB和ProductC三种产品了,此时,需要增加生产ProductD产品;那么,首先是不是需要在产品枚举类型中添加新的产品类型标识,然后,修改Factory类中的switch结构代码。这种对代码的修改量较大,易产生编码上的错误。

工厂方法模式是简单工厂模式的进一步抽象和推广。由于使用了面向对象的多态性,工厂方法模式保持了简单工厂模式的优点,而且克服了它的缺点。

在工厂方法模式中,核心的工厂类不再负责所有产品的创建,而是将具体创建工作交给子类去做。这个核心类仅仅负责给出具体工厂必须实现的接口,而不负责哪一个产品类被实例化这种细节,这使得工厂方法模式可以允许系统在不修改工厂角色的情况下引进新产品。

相似的产品有相同的操作,我们对产品的接口操作,让产品的生成延迟到子类。(这些操作是固定的,但操作的目标是变化的)

不同的子类生产不同的产品,你选择了哪种子类,就相当于你选择了哪种产品。



简单示例代码:

class Factory
{
public:
    virtual Product *CreateProduct() = 0;
    void OperateProduct() ;  //对产品的操作框架
private:
    Product * productPtr ;
};

class FactoryA : public Factory
{
public:
    Product *CreateProduct()
    {
        return new ProductA ();
    }
};

class FactoryB : public Factory
{
public:
    Product *CreateProduct()
    {
        return new ProductB ();
    }
};

//对产品的操作,这些产品有共同的接口
void Factory::OperateProduct()
{
    //我们不知道具体是哪种产品被创建
    Product * productPtr = CreateProduct() ; 
    //但是我们知道怎么使用这个产品
    productPtr->show();                      
}
int main(void)
{
    //当我们选择了不同的工厂,我们实际就选择了相应的产品
    Factory *factory = new FactoryB ();
    factory->OperateProduct();

    //........
}


【思考】

1、这和我们直接new一个产品对象有什么区别?

Product* product = new ProductB() ;
    product->show() ;
    product->change() ;
    区别是我们的工厂类中有围绕着产品进行的对产品的操作。我们使用这个操作框架(我们保证这个操作框架是基本不变的)。我们直接对产品进行操作而不通过工厂类,则相同的对产品的操作代码会散落在各处,代码重用性不好。
    我们就是在一个框架中使用工厂方法的,框架使用抽象类定义对象之间的关系,这些对象的创建通常也由框架负责。(我们就是为了代码重用才使用框架的,不会说是直接使用产品类,在客户代码中再重写这些与产品相关的框架代码)

    【类似模板方法】
    工厂方法通常在模板方法中被调用。


2、使用模板以避免创建子类

从上面的例子看到,在使用工厂方法时,我们要new一个相应的工厂类,然后在使用完毕后需要delete它。这样做比较繁琐。
    解决方式:我们可以把new的工厂类放入auto_ptr智能指针类,由智能指针负责管理我们动态分配的对象。
    或者,把工厂类设计成模板类


即:

template <class Product>
Product* StdFactory : public Factory
{
public:
    virtual Product* CreateProduct()
    {
        return new Product ;
    }
}

使用这个模板,我们就不需要再new工厂的子类了
StdFactory<ProductB> FactoryB ;


【缺点】

在添加新产品时,需要编写新的具体产品类,而且还要提供与之对应的具体工厂类,系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,有更多的类需要编译和运行,会给系统带来一些额外的开销。

【使用场景】

1、当一个类不知道它所必须创建的对象的类的时候。(我现在需要使用这个类对象,但我又不知道这个类对象如何实例化)

2、当一个类希望由它的子类来指定它所创建的对象的时候。

相关知识:

C++的智能指针(我们可以在工厂类中添加一个智能指针来存储new的产品对象)

抽象工厂

【把一系列相关的组件,放在一个工厂创建,这样只需换一个工厂,就可以换一个产品系列】

(变化的是产品的形态种类,不变的是对产品的操作集)

抽象工厂是诸多设计模式中最为“宏伟”的一个模式。(这里说的宏伟,意为更换一个抽象工厂,对系统的面貌影响较大)

【动机】

在工厂方法模式中具体工厂负责生产具体的产品,每一个具体工厂对应一种具体产品,但是有时候我们需要一个工厂可以提供多个产品对象,而不是单一的产品对象。

可以用“一横一竖”来概括抽象工厂。

一横(产品的继承结构):横向扩展,平级的产品,如一个抽象类是电视机,其子类有海尔电视机、海信电视机、TCL电视机。抽象电视机是父类,而具体品牌的电视机是其子类。

一竖(产品族):指由同一个工厂生产的,位于不同产品结构中的一组产品,如海尔电器工厂生产的海尔电视机、海尔电冰箱,海尔电视机位于电视机产品结构中,海尔电冰箱位于电冰箱产品结构中。

当你要强调一系列相关的产品对象的设计以便进行联合使用时。



【缺点】

纵向扩展比较困难比如添加一个新的产品种类(比如:手机产品),这将涉及到对抽象工厂角色及其所有子类的修改,显然会带来较大的不便。(需要对海尔厂、海信厂、TCL厂都进行修改,添加它们的手机产品)

【使用场景】

一个系统要由多个产品系列中的一个来配置时。

当你要强调一系列相关产品对象的设计以便进行联合使用时。

在很多软件系统中需要更换界面主题,要求界面中的按钮、文本框、背景色等一起发生改变时,可以使用抽象工厂模式进行设计。

一个抽象工厂创建了一个完整的产品系列。

抽象工厂只负责创建这些种类的产品,而不负责组装这些产品为成品。

建造者模式

【将一个对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示】

(变化的是零件,不变的是对这些零件的组装过程)

【动机】

无论是在现实世界中还是在软件系统中,都存在一些复杂的对象,它们拥有多个组成部分,如汽车,它包括车轮、方向盘、发送机等各种部件。而对于大多数用户而言,无须知道这些部件的装配细节,也几乎不会使用单独某个部件,而是使用一辆完整的汽车,可以通过建造者模式对其进行设计与描述,建造者模式可以将部件和其组装过程分开,一步一步创建一个复杂的对象。用户只需要指定复杂对象的类型就可以得到该对象,而无须知道其内部的具体构造细节。

复杂对象相当于一辆有待建造的汽车,而对象的属性相当于汽车的部件,建造产品的过程就相当于组合部件的过程。由于组合部件的过程很复杂,因此,这些部件的组合过程往往被“外部化”到一个称作建造者的对象里,建造者返还给客户端的是一个已经建造完毕的完整产品对象,而用户无须关心该对象所包含的属性以及它们的组装方式,这就是建造者模式的模式动机。

这样的话,我们可以方便地改变产品的内部表示,即可以方便地更换零件,产品的总结构不变。



示例代码

int main(void)
{
    ConcreteBuilder * builder = new ConcreteBuilder();
    Director  director;
    director.setBuilder(builder);
    Product * pd =  director.constuct();
    pd->show();

    //......
}

Product* Director::constuct()
{
    m_pbuilder->buildPartA();
    m_pbuilder->buildPartB();
    m_pbuilder->buildPartC();

    return m_pbuilder->getResult();
}


【优点】

在建造者模式中, 客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。

每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者, 用户使用不同的具体建造者即可得到不同的产品对象 。(有点像抽象工厂)

【缺点】

建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式。

【适用情景】

需要生成的产品对象有复杂的内部结构,且这些产品对象的属性相互依赖,需要指定其生成顺序。

隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品。

【就是,当需要创建由许多零件装配的对象时,使用建造者模式】

在很多游戏软件中,地图包括天空、地面、背景等组成部分,人物角色包括人体、服装、装备等组成部分,可以使用建造者模式对其进行设计,通过不同的具体建造者创建不同类型的地图或人物。

【总结】

Builder类与抽象工厂类十分相似,但抽象工厂创建的是一系列有关的产品,建造者模式创建的是一系列有关的零件,最后还需要导演类来组装零件成产品。(多了一个导演类来组装零件,两者的关注点不同)
    注意:导演只是对组装过程进行引导,最终还是builder组装并产出产品。(导演类中是包含建造者的,所有创建工作由建造者负责完成)导演调用builder一步一步把零件填装到其空骨架中,最终返回一个成品。
    如果将抽象工厂模式看成 汽车配件生产工厂 ,生产一个产品族的产品,那么建造者模式就是一个 汽车组装工厂 ,通过对部件的组装可以返回一辆完整的汽车。

    将工厂模式稍加变化可以得到建造者(Builder)模式。工场模式的“加工工艺”是隐藏的,而建造模式的“加工工艺”是暴露的。把工厂方法的一个方法分成做个方法步骤去构造一个产品,即为建造者模式。


单件模式

【用static函数返回此单件的指针】

懒汉式

懒汉式的特点是延迟加载,比如配置文件的单例,采用懒汉式的方法,顾名思义,懒汉么,很懒的,配置文件的实例直到用到的时候才会加载。

class Singleton
{
public:
    static Singleton* Instance() ;
protected:
    Singleton() ;
private:
    static Singleton* _instance ;
} ;

//实现
Singleton* Singleton::_instance = 0 ;
Singleton* Singleton::Instance()
{
    if (_instance == 0)
    {
        _instance = new Singleton ;
    }

    return _instance ;
}


这种简单的懒汉式单例,new出来的东西始终没有释放,虽然只有一份,不太可能造成内存泄露。

但我们可以在单例类中添加一个静态对象专门用来释放new出来的单例。

改进版懒汉:

class Singleton  
{  
public:  
    static Singleton * GetInstance()  
    {  
        if(_instance == NULL)  
            _instance = new CSingleton();  
        return _instance;  
    }  
protected:  
    CSingleton() { }  
private:
    static Singleton *_instance; 

    class CFreeInstance   
    {  
    public:  
        ~CFreeInstance()  
        {  
            if(Singleton::_instance)  
                delete CSingleton::_instance;  
        }  
    };  
    static CFreeInstance aFree;   

};


饿汉式:

懒汉式单例有线程安全问题,在if判断_instance是否为NULL时,多线程访问会出现安全问题。

饿汉式的特点是一开始就加载了,饿汉式是线程安全的,在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变。

class Singleton  
{  
private:  
    Singleton() { }  
public:  
    static Singleton * GetInstance()  
    {  
        static Singleton _instance;   
        return &_instance;  
    }  
};


【参考】

《设计模式之禅》

《GoF设计模式》
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: