设计模式之:创建型设计模式(6种)
创建型设计模式有: 共6种
- 简单工厂模式(Simple Factory)
- 工厂方法模式(Factory Method)
- 抽象工厂模式(Abstract Factory)
- 建造者模式(Builder)
- 原型模式(Prototype)
- 单例模式(Singleton)
简单工厂模式
功能:主要用于创建对象。新添加类时,不会影响以前的系统代码。核心思想是用一个工厂来根据输入的条件产生不同的类,然后根据不同类的virtual函数得到不同的结果。
优点: 适用于不同情况创建不同的类时
缺点: 客户端必须要知道基类和工厂类,耦合性差
模式结构:
- Factory:工厂角色负责实现创建所有实例的内部逻辑
- Product:抽象产品角色是所创建的所有对象的父类,负责描述所有实例所共有的公共接口
- ConcreteProduct:具体产品角色是创建目标,所有创建的对象都充当这个角色的某个具体类的实例。
模式应用:
C++举例:
//基类 class COperation { public: int m_nFirst; int m_nSecond; virtual double GetResult() { double dResult=0; return dResult; } };
//加法 class AddOperation : public COperation { public: virtual double GetResult() { return m_nFirst+m_nSecond; } };
//减法 class SubOperation : public COperation { public: virtual double GetResult() { return m_nFirst-m_nSecond; } }; //工厂类 class CCalculatorFactory { public: static COperation* Create(char cOperator); //基类和工厂类是关联关系 }; COperation* CCalculatorFactory::Create(char cOperator) { COperation *oper; switch (cOperator) { case '+': oper=new AddOperation(); break; case '-': oper=new SubOperation(); break; default: oper=new AddOperation(); break; } return oper; } //客户端 int main() { int a,b; cin>>a>>b; COperation * op=CCalculatorFactory::Create('-'); //静态函数直接调用 op->m_nFirst=a; op->m_nSecond=b; cout<<op->GetResult()<<endl; return 0; }
Java举例:
工厂方法模式(多态工厂模式)
功能:定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
优点:
- 向客户隐藏了哪种具体产品将被实例化的细节,用户只需要关心所需产品对应的工厂,无须关心创建细节,无须知道具体产品类的类名。
- 基于工厂角色和产品角色的多态性设计是工厂方法模式的关键。它能够使工厂可以自主确定创建何种产品对象,而如何创建这个对象的细节则完全封装在具体工厂内部。工厂方法模式之所以又被称为多态工厂模式,是因为所有的具体工厂类都具有同一抽象父类。
- 系统中加新产品时,无须修改抽象工厂和抽象产品的接口,(但要修改客户端,以选择不同的具体工厂!),无须修改其他的具体工厂和产品,而只要添加一个具体工厂和具体产品即可。符合“开闭原则”。
缺点:
- 在添加新产品时,需要编写新的具体产品类,而且还要提供与之对应的具体工厂类,系统中类的个数将成对增加,一定程度上增加了系统复杂度,有更多的类需要编译和运行,会给系统带来一些额外的开销。
- 由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度,且在实现时可能需要用到DOM、反射等技术,增加了系统的实现难度。
模式应用:
JDBC中的工厂方法
举例:
日志记录器:
某系统日志记录器要求支持多种日志记录方式,如文件记录、数据库记录等,且用户可以根据要求动态选择日志记录方式, 现使用工厂方法模式设计该系统。
C++举例:
#include <string> #include <iostream> using namespace std; //实例基类,相当于Product(为了方便,没用抽象) class LeiFeng { public: virtual void Sweep() { cout<<"雷锋扫地"<<endl; } }; //学雷锋的大学生,相当于ConcreteProduct(具体产品1) class Student: public LeiFeng { public: virtual void Sweep() { cout<<"大学生扫地"<<endl; } }; //学雷锋的志愿者,相当于ConcreteProduct(具体产品2) class Volenter: public LeiFeng { public : virtual void Sweep() { cout<<"志愿者"<<endl; } }; ----------------------------- //工场基类Creator class LeiFengFactory { public: virtual LeiFeng* CreateLeiFeng() //返回值体现依赖关系 { return new LeiFeng(); } }; //工场具体类1(创建大学生产品的具体工厂) class StudentFactory : public LeiFengFactory { public : virtual LeiFeng* CreateLeiFeng() //返回值体现依赖关系 { return new Student(); } }; //工场具体类2(创建志愿者产品的具体工厂) class VolenterFactory : public LeiFengFactory { public: virtual LeiFeng* CreateLeiFeng() { return new Volenter(); //返回值体现依赖关系 } }; ----------------------------- //客户端 int main() { LeiFengFactory *sf=new StudentFactory(); //由客户端选创建什么产品 LeiFeng *s=sf->CreateLeiFeng(); s->Sweep(); delete s; delete sf; return 0; }
Java举例:
抽象工厂模式(Kit模式)
功能:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
优点:
- 抽象工厂模式隔离了具体类的生成,使得客户并不需要知道什么被创建。由于这种隔离,更换一个具体工厂就变得容易。所有的具体工厂都实现了抽象工厂中定义的那些公共接口,因此只需改变具体工厂的实例,就可以在某种程度上改变整个软件系统的行为。另外,此模式可以实现高内聚低耦合的设计目的。
- 当一个产品族中的多个对象被设计成一起工作时,它能够保证客户端始终只使用同一个产品族中的对象。这对一些需要根据当前环境来决定其行为的软件系统来说,很实用。
- 增加新的具体工厂和产品族很方便,无须修改已有系统,符合“开闭原则”。
缺点:
- 在添加新的产品对象时,难以扩展抽象工厂来生产新种类的产品,这是因为在抽象工厂角色中规定了所有可能被创建的产品集合,要支持新种类的产品就意味着要对该接口进行扩展,而这将涉及到对抽象工厂角色及其所有子类的修改,显然会带来较大的不便。
- 开闭原则的倾斜性(增加新的工厂和产品族容易,增加新的产品等级结构麻烦)。
模式应用:
在很多软件系统中需要更换界面主题,要求界面中的按钮、文本框、背景色等一起发生改变时,可以使用抽象工厂模式进行设计。
C++举例:
#include <string> #include <iostream> #include <vector> using namespace std; //用户抽象接口(抽象产品A) class IUser { public : virtual void GetUser()=0; virtual void InsertUser()=0; }; //部门抽象接口(抽象产品B) class IDepartment { public: virtual void GetDepartment()=0; virtual void InsertDepartment()=0; }; //ACCESS用户(抽象产品A的产品1) class CAccessUser : public IUser { public: virtual void GetUser() { cout<<"Access GetUser"<<endl; } virtual void InsertUser() { cout<<"Access InsertUser"<<endl; } }; //ACCESS部门(抽象产品B的产品1) class CAccessDepartment : public IDepartment { public: virtual void GetDepartment() { cout<<"Access GetDepartment"<<endl; } virtual void InsertDepartment() { cout<<"Access InsertDepartment"<<endl; } }; //SQL用户(抽象产品A的产品2) class CSqlUser : public IUser { public: virtual void GetUser() { cout<<"Sql User"<<endl; } virtual void InsertUser() { cout<<"Sql User"<<endl; } }; //SQL部门类(抽象产品B的产品2) class CSqlDepartment: public IDepartment { public: virtual void GetDepartment() { cout<<"sql getDepartment"<<endl; } virtual void InsertDepartment() { cout<<"sql insertdepartment"<<endl; } }; //抽象工厂 class IFactory { public: virtual IUser* CreateUser()=0; virtual IDepartment* CreateDepartment()=0; }; //ACCESS工厂 class AccessFactory : public IFactory { public: virtual IUser* CreateUser() { return new CAccessUser(); } virtual IDepartment* CreateDepartment() { return new CAccessDepartment(); } }; //SQL工厂 class SqlFactory : public IFactory { public: virtual IUser* CreateUser() { return new CSqlUser(); } virtual IDepartment* CreateDepartment() { return new CSqlDepartment(); } }; //客户端: int main() { IFactory* factory= new SqlFactory(); IUser* user=factory->CreateUser(); IDepartment* depart = factory->CreateDepartment(); user->GetUser(); depart->GetDepartment(); return 0; }
Java举例:
建造者模式(生成器模式)
功能:将一个复杂对象的构建与表示分离,使得同样的构建过程可以创建不同的表示。
优点:
- 客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
- 每一个具体建造者都独立,因此可以方便地替换具体建造者或增加新的具体建造者, 用户使用不同的具体建造者即可得到不同的产品对象 。
- 可以更加精细地控制产品的创建过程 。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。
- 增加新的具体建造者无须修改原有类库的代码,指挥者类针对抽象建造者类编程,系统扩展方便,符合“开闭”。
缺点:
- 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,若产品之间的差异性很大,则不适合使用该模式,因此其使用范围受到一定限制。
- 若产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大。
模式应用:
很多游戏软件中,地图包括天空、地面、背景等组成部分,人物角色包括人体、服装、装备等组成部分,可以使用建造者模式对其进行设计,通过不同的具体建造者创建不同类型的地图或人物。
举例: KFC套餐
套餐是一个复杂对象,一般包含主食(如汉堡、鸡肉卷等)和饮料(如果汁、 可乐等)等部分,不同的套餐有不同的组成部分,而KFC的服务员可以根据顾客的要求,一步一步装配这些组成部分,构造一份完整的套餐,然后返回给顾客。
C++举例:
#include <string> #include <iostream> #include <vector> using namespace std; //最终的产品类 class Product { private: vector<string> m_product; public: void Add(string strtemp) { m_product.push_back(strtemp); } void Show() { vector<string>::iterator p=m_product.begin(); while (p!=m_product.end()) { cout<<*p<<endl; p++; } } }; //建造者基类 class Builder { public: virtual void BuilderA()=0; virtual void BuilderB()=0; virtual Product* GetResult()=0; }; //第一种建造方式 class ConcreteBuilder1 : public Builder { private: Product* m_product; //体现依赖关系 public: ConcreteBuilder1() { m_product=new Product(); } virtual void BuilderA() { m_product->Add("one"); } virtual void BuilderB() { m_product->Add("two"); } virtual Product* GetResult() { return m_product; } }; //第二种建造方式 class ConcreteBuilder2 : public Builder { private: Product * m_product; //体现依赖关系 public: ConcreteBuilder2() { m_product=new Product(); } virtual void BuilderA() { m_product->Add("A"); } virtual void BuilderB() { m_product->Add("B"); } virtual Product* GetResult() { return m_product; } }; //指挥者类 class Direct { public: void Construct(Builder* temp) //构建一个使用Builder接口的对象 { temp->BuilderA(); temp->BuilderB(); } }; //客户端 int main() { Direct *p=new Direct(); Builder* b1=new ConcreteBuilder1(); Builder* b2=new ConcreteBuilder2(); p->Construct(b1); //调用第一种建造方式 Product* pb1 = b1->GetResult(); pb1->Show(); p->Construct(b2); //调用第二种建造方式 Product * pb2 = b2->GetResult(); pb2->Show(); return 0; }
单例模式
功能:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
优点:
- 提供了对唯一实例的受控访问。因为单例类封装了它的唯一实例,所以它可以严格控制客户怎样以及何时访问它,并为设计及开发团队提供了共享的概念。
- 由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象,单例模式无疑可以提高系统的性能。
- 允许可变数目的实例。我们可以基于单例模式进行扩展,使用与单例控制相似的方法来获得指定个数的对象实例。
缺点:
- 由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。
- 单例类的职责过重,在一定程度上违背了“单一职责原则”。因为单例类既充当了工厂角色,提供了工厂方法,同时又充当了产品角色,包含一些业务方法,将产品的创建和产品的本身的功能融合到一起。
- 滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;现在很多面向对象语言(如Java、C#)的运行环境都提供了自动垃圾回收的技术,因此,如果实例化的对象长时间不被利用,系统会认为它是垃圾,会自动销毁并回收资源,下次利用时又将重新实例化,这将导致对象状态的丢失。
模式应用:
一个具有自动编号主键的表可以有多个用户同时使用,但数据库中只能有一个地方分配下一个主键编号,否则会出现主键重复,因此该主键编号生成器必须具备唯一性,可以通过单例模式来实现。
举例:
在OS中,打印池(Print Spooler)是一个用于管理打印任务的应用程序,通过打印池用户可以删除、中止或者改变打印任务的优先级,在一个系统中只允许运行一个打印池对象,如果重复创建打印池则抛出异常。现使用单例模式来模拟实现打印池的设计。
C++举例:
懒汉式单例:
#include <iostream> #include <string> #include <vector> using namespace std; class Singelton { private: Singelton(){ } //构造函数私有堵死了外界创建副本的可能!!! static Singelton* singel; //私有的静态全局变量来保存该类的唯一实例 public: static Singelton* GetInstance() //获得本类实例的唯一全局访问点 { if(singel == NULL) { singel = new Singelton(); } return singel; } }; Singelton* Singelton::singel = NULL;//注意静态变量类外初始化 //客户端: int main() { Singelton* s1=Singelton::GetInstance(); Singelton* s2=Singelton::GetInstance(); if(s1 == s2) //比较两次实例化后的结果是实例相同!!! cout<<"ok"<<endl; else cout<<"no"<<endl; return 0; }
Java举例:
原型模式
功能:用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。原型模式其实就是从一个对象创建另外一个可定制的对象,而且不需知道任何创建的细节。
优点:
一般在初始化的信息不发生变化的情况下,克隆是最好的办法,既隐藏了对象创建细节,又提高性能。其等于是不用重新初始化对象,而是动态地获得对象运行时的状态。
缺点:
模式应用:
C++举例
#include<iostream> #include <vector> #include <string> using namespace std; //抽象基类 class Prototype { private: string m_strName; public: Prototype(string strName){ m_strName = strName; } Prototype() { m_strName = " "; } void Show() { cout<<m_strName<<endl; } virtual Prototype* Clone() = 0 ; //关键就在于这样一个抽象方法 } ; // class ConcretePrototype1 class ConcretePrototype1 : public Prototype { public: ConcretePrototype1(string strName) : Prototype(strName){} ConcretePrototype1(){} virtual Prototype* Clone() { ConcretePrototype1 *p = new ConcretePrototype1() ; *p = *this ; //复制对象 return p ; } } ; // class ConcretePrototype2 class ConcretePrototype2 : public Prototype { public: ConcretePrototype2(string strName) : Prototype(strName){} ConcretePrototype2(){} virtual Prototype* Clone() { ConcretePrototype2 *p = new ConcretePrototype2() ; *p = *this ; //复制对象 return p ; } } ; //客户端 int main() { ConcretePrototype1* test = new ConcretePrototype1("小王"); ConcretePrototype2* test2 = (ConcretePrototype2*)test->Clone(); test->Show(); test2->Show(); return 0; }