设计模式-创建者模式

1.创建者模式

创建者模式的主要关注点是“怎样创建对象?”,它的主要特点是“将对象的创建与使用分离”。这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。

1.单例模式

1.概述
定义: 确保一个类最多只有一个实例,并提供一个全局访问点
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
2.结构

单例模式的主要有以下角色:

  • 单例类。只能创建一个实例的类
  • 访问类。使用单例类

3.实现
单例设计模式分类两种:
饿汉式:类加载就会导致该单实例对象被创建
懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时被创建。

3.1 预加载(饿汉式)

预加载:顾名思义,就是预先加载。再进一步解释就是还没有使用该单例对象,但是,该单例对象就已经被加载到内存了。

//Singleton.cpp
#include<iostream>
using namespace std;
class Singleton;
class Singleton {
private:
    Singleton() {};//构造函数私有化
    Singleton(Singleton &s) {}; //拷贝构造私有化
    Singleton& operator=(const Singleton& s) {};
    static Singleton* singletonInstance;
public:
    static Singleton* getInstance() {
        return singletonInstance;
    }
};
//......
Singleton* Singleton::singletonInstance = new Singleton();//全局实例化

小结:
很明显,没有使用该单例对象,该对象就被加载到了内存,会造成内存的浪费。
该方式在成员位置声明Singleton类型的静态变量,并创建Singleton类的对象instance。instance对象是随着类的加载而创建的。如果该对象足够大的话,而一直没有使用就会造成内存的浪费。

2.3 单例模式的GC

1.单例模式中实例化的对象是new出来的,需要对它负责,也就是需要delete;
2.delete一个对象时,会调用它的析构函数;
3.如果在析构函数里使用delete进行内存管理,由于对象是new出来的,根本进不去析构函数,无法自动的销毁;
4.如果在析构函数里使用delete进行内存管理,就一定会造成一个析构的循环的(见2)。

那么应该怎样在C++的程序中显式的进行单例对象的内存管理呢?一个比较好的方法是定义一个内部垃圾回收类,并且在Singleton中定义一个此类的静态成员。程序结束时,系统会自动析构此静态成员,此时,在此类的析构函数中析构Singleton实例,就可以实现singletonInstance的自动释放。

//Singleton.cpp
#include<iostream>
using namespace std;
class Singleton;
class Singleton {
private:
    Singleton() { cout << "Construct singleton." << endl; };//构造函数私有化
    Singleton(Singleton &s) {}; //拷贝构造私有化
    Singleton& operator=(const Singleton& s) {};
    static Singleton* singletonInstance;

    class SingletonCG {//单例的回收
    public:
        ~SingletonCG(){
            if (singletonInstance != NULL)
                delete singletonInstance;
            singletonInstance = NULL;
            cout << "Singleton delete." << endl;
            system("pause");
        }
    };
    static SingletonCG cg;
public:
    static Singleton* getInstance() {
        cout << "getInstance." << endl;
        return singletonInstance;
    }
    
};

Singleton* Singleton::singletonInstance = new Singleton();//全局实例化
Singleton::SingletonCG Singleton::cg;类的静态成员需要类外部初始化,这一点很重要,否则程序运行连GC的构造都不会进入,何谈自动析构

3.2 预加载(饿汉式)——枚举方式

枚举类实现单例模式是极力推荐的单例实现模式,因为枚举类型是线程安全的,并且只会装载一次,设计者充分的利用了枚举的这个特性来实现单例模式,枚举的写法非常简单,而且枚举类型是所用单例实现中唯一一种不会被破坏的单例实现模式。

/**
 * 枚举方式实现单例
 * 枚举类型实现单例模式是极力推荐的实现模式。因为枚举是线程安全的,并且只会装载一次,设计者充分
 * 利用了枚举的这个特性来实现单例模式,枚举的写法非常简单,而且枚举类型是所用单例实现中唯一不会被
 * 破坏的单例实现模式。
 *
 * 枚举类型属于饿汉式方式,即预加载模式,在不考虑内存空间的情况下,使用枚举方式是比较好的一种方式
 */
public enum Singleton6 {
    INSTANCE;
}

3.3 懒加载(懒汉式)

为了避免内存的浪费,我们可以采用懒加载,即用到该单例对象的时候再创建。

class CSingleton {
public:
    static CSingleton* getInstance() {
        //多线程这里会出现线程安全问题。
        if (single == nullptr) {
            single = new CSingleton();
        }
        return single;
    }

private:
    static CSingleton* single;
    CSingleton();
    ~CSingleton();
    CSingleton(const CSingleton&){}

    //注意如果没有下面的释放资源类,在main函数中将手动释放资源
    class CRelease {
    public:
        ~CRelease() { delete single; }
    };
    static CRelease release;

};
CSingleton* CSingleton::single = nullptr;
CSingleton::CRelease CSingleton::release;

从上面代码我们可以看出该方式在成员位置声明Singleton类型的静态变量,并没有进行对象的赋值操作,那么什么时候赋值的呢?当调用getInstance()方法获取Singleton类的对象的时候才创建Singleton类的对象,这样就实现了懒加载的效果。但是,如果是多线程环境,会出现线程安全问题。

3.线程安全单例模式

考虑在多线程下的情景

3.1 饿汉单例模式的线程安全特性
饿汉单例模式中,单例对象定义成了一个static静态对象,它是在程序启动时,main函数运行之前就初始化好的,因此不存在线程安全问题,可以放心的在多线程环境中使用。

3.2 懒汉单例模式的线程安全特性
getInstance是个不可重入函数,也就它在多线程环境中执行,会出现竞态条件问题。因此需要考虑加锁(很明显是懒汉的 饿汉不存在线程不安全)

class CSingleton
{
public:
	static CSingleton* getInstance()
	{
		// 获取互斥锁
		pthread_mutex_lock(&mutex);
		if (nullptr == single)
		{
			single = new CSingleton();
		}
		// 释放互斥锁
		pthread_mutex_unlock(&mutex);
		return single;
	}
private:
	static CSingleton *single;
	CSingleton() 
	~CSingleton() 
	{ 
		pthread_mutex_destroy(&mutex); // 释放锁
	}
	CSingleton(const CSingleton&);

	class CRelease
	{
	public:
		~CRelease() { delete single; }
	};
	static CRelease release;
	
	// 定义线程间的互斥锁
	static pthread_mutex_t mutex;
};
CSingleton* CSingleton::single = nullptr;
CSingleton::CRelease CSingleton::release;
// 互斥锁的初始化
pthread_mutex_t CSingleton::mutex = PTHREAD_MUTEX_INITIALIZER;

单例模式中的双检锁

2.1 模板形式单例模式

使用了两个 if判断语句的技术称为双检锁;好处是,只有判断指针为空的时候才加锁,避免每次调用 get_instance的方法都加锁,因为锁的开销较大.

class CSingleton
{
public:
	static CSingleton* getInstance()
	{
         static std::mutex m_mutex;
		if (nullptr == single)
		{
            //std::lock_guard需要在作用域范围开头定义,也可以通过块操作限制其作用域范围
             std::lock_guard<std::mutex> lock(m_mutex);
             if (nullptr == m_pInstance) {
                 single = new CSingleton();
             }
         } 
		return single;
	}
private:
	static CSingleton *single;
	CSingleton() 
	~CSingleton() 
	{ 
		pthread_mutex_destroy(&mutex); // 释放锁
	}
	CSingleton(const CSingleton&);

	class CRelease
	{
	public:
		~CRelease() { delete single; }
	};
	static CRelease release;

};
CSingleton* CSingleton::single = nullptr;
CSingleton::CRelease CSingleton::release;
#include <iostream>
#include <cassert>
#include <vector>
#include <mutex>
#include <memory>
using namespace std;
 
template<typename T>
class Singleton {
 
public:
    //支持0个参数的构造函数
    static T* Instance()
    {
        if (m_pInstance == nullptr) {
            m_pInstance = new T();
        }
        return m_pInstance;
    }
    //支持1个参数的构造函数
    template<typename T0>
    static T* Instance(T0 arg0)
    {
        if (m_pInstance == nullptr) {
            m_pInstance = new T(arg0);
        }
        return m_pInstance;
    }
    //支持2个参数的构造函数
    template<typename T0, typename T1>
    static T* Instance(T0 arg0, T1 arg1)
    {
        if (m_pInstance == nullptr) {
            m_pInstance = new T(arg0, arg1);
        }
        return m_pInstance;
    }
    //获取单例
    static T* GetInstance() {
        static std::mutex m_mutex;
        if (nullptr == m_pInstance) {
            std::lock_guard<std::mutex> lock(m_mutex);
            if (nullptr == m_pInstance) {
                throw std::logic_error("the instance is not init");
            }
        }
        return m_pInstance.get();
    }
    //释放单例
    static void DestroyInstance() {
        delete m_pInstance;
        m_pInstance = nullptr;
    }
private:
    static T* m_pInstance;//私有的静态指针指向唯一的实例
    //static std::shared_ptr<T> m_pInstance;//定义为智能指针不需要手动释放
private:
    //重载操作符即拷贝构造函数设置为private,避免对象间复制和赋值
    Singleton(void);//私有的构造函数
    virtual  ~Singleton(void) {};
    Singleton(const Singleton&);
    Singleton& openrator = (const Singleton&);
};
template<typename T>
T* Singleton<T>::m_pInstance = nullptr;
//测试单例
class A {
public:
    A() {
        cout << "Create A" << endl;
    }
};
class B {
public:
    B(int x) { cout << "Create B" << endl; }
};
class C {
public:
    C(int x, double y) { cout << "Create C" << endl; }
};
 
int main()
{
 
    Singleton<A>::Instance();
    //创建B类型的单例
    Singleton<B>::Instance(1);
    //创建C类型的单例
    Singleton<C>::Instance(1, 2.1);
 
    Singleton<A>::DestroyInstance();
    Singleton<B>::DestroyInstance();
    Singleton<C>::DestroyInstance();
    return 0;
}
//https://blog.csdn.net/kenjianqi1647/article/details/123165112?spm=1001.2101.3001.6650.14&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7ERate-14-123165112-blog-24853471.pc_relevant_antiscanv2&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7ERate-14-123165112-blog-24853471.pc_relevant_antiscanv2&utm_relevant_index=22

2.3 单例初始化确保安全

单例类的初始化,考虑到多线程安全,需要进行加锁控制。C++11中提供的call_once可以很好的满足这种需求。

std::call_once使用方法

第一个参数是std::once_flag的对象(once_flag是不允许修改的,其拷贝构造函数和operator=函数都声明为delete),第二个参数可调用实体,即要求只执行一次的代码,后面可变参数是其参数列表

call_once保证函数fn只被执行一次,如果有多个线程同时执行函数fn调用,则只有一个活动线程(active call)会执行函数,其他的线程在这个线程执行返回之前会处于”passive execution”(被动执行状态)——不会直接返回,直到活动线程对fn调用结束才返回。

#include <mutex>
#include <thread>
#include <memory>
template<typename T>
class Singleton {
 public:
    static T* GetInstance() {
        if (nullptr == m_instance_) {
            std::call_once(m_flag_, []{m_instance_=std::make_shared<T>();});
        }
        return m_instance_.get();
    }
 
 
 private:
    static std::once_flag m_flag_;
    static std::shared_ptr<T> m_instance_;
};
 
template<typename T>
std::shared_ptr<T> Singleton<T>::m_instance_ = nullptr;
 
template<typename T>
std::once_flag Singleton<T>::m_flag_;

2.说一说单例模式

确保一个类只有一个实例,并提供一个全局的访问点。为了防止频繁地创建对象使得内存飙升,单例模式可以让程序仅在内存中创建一个对象,让所有需要调用的地方都共享这一单例对象。
单例模式主要有两种类型:
饿汉式:类加载就会导致该单实例对象被创建
懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时被创建。

2.工厂模式

需求:设计一个咖啡店点餐系统。
设计一个咖啡类(Coffee),并定义其两个子类(美式咖啡【AmericanCoffee】和拿铁咖啡【LatteCoffee】);再设计一个咖啡店类(CoffeeStore),咖啡店具有点咖啡的功能。

img

1.实现:不用设计模式

咖啡类,抽象类

class Coffee {
public:
    Coffee();
    virtual ~Coffee();
    virtual string getName();
	//加糖
	void addSugar() {
		cout<<("加糖");
	}
	//加奶
	void addMilk() {
		cout<<("加奶");
	}
}

美式咖啡

class AmericanCoffee :public Coffee{
public:
    AmericanCoffee();
    virtual ~AmericanCoffee();
    string getName() {
		// TODO Auto-generated method stub
		return "美式咖啡";
	}
}

拿铁咖啡

class LatteCoffee  :public Coffee{
public:
    LatteCoffee ();
    virtual ~LatteCoffee ();
    string getName() {
		// TODO Auto-generated method stub
		return "拿铁咖啡";
	}
}

由CoffeStore来制作不同的咖啡

class CoffeeStore {
public: 
    Coffee orderCoffee(string type) {
		//声明Coffee类型变量,根据不同类型创建不同的Coffee子类对象
		Coffee* coffee=NULL;
		if("american"==(type)) {
			coffee=new AmericanCoffee();
		}else if("latte"==(type)) {
			coffee=new LatteCoffee();
		}else {
			throw new RuntimeException("对不起,你点的咖啡没有");
		}
		//加配料
		coffee->addMilk();
		coffee->addSugar();
		return coffee;
	}

客户端

void main(String[] args) {
	//1.创建咖啡店
	CoffeeStore *store=new CoffeeStore();
	//2.点咖啡
	Coffee *coffee= store->orderCoffee("american");
	cout<<(coffee.getName());
}

对象都需要创建,如果创建的时候直接new该对象,就会对该对象耦合严重,假如我们要更换对象所有new对象的地方都需要修改一遍,这显然违背了软件设计的开闭原则**。如果我们使用工厂来生产对象,我们就只和工厂打交道就可以了,彻底和对象解耦,如果要更换对象,直接在工厂里更换该对象即可,达到了与对象解耦的目的**;所以说,工厂模式最大的优点就是:解耦

2.简单工厂模式。(不是一种设计模式)

2.1简单工厂角色

  • 抽象产品 :定义了产品的规范,描述了产品的主要特性和功能。(Coffee)
  • 具体产品 :实现或者继承抽象产品的子类(美式咖啡或拿铁咖啡)
  • 具体工厂提供了创建产品的方法,调用者通过该方法来获取产品。(生产咖啡的工厂)

img
简单咖啡工厂,专门用来生产咖啡

class SimpleCoffeeFactory {
public:
    Coffee* createCoffee(string type) {
		
		//声明Coffee类型变量,根据不同类型创建不同的Coffee子类对象
		Coffee* coffee=NULL;
		if("american"==(type)) {
			coffee=new AmericanCoffee();
		}else if("latte"==(type)) {
			coffee=new LatteCoffee();
		}else {
			throw new RuntimeException("对不起,你点的咖啡没有");
		}
		return coffee;
	}
};

咖啡店使用简单工厂来生产不同咖啡

class CoffeeStore {
public:
    Coffee* orderCoffee(String type) {
		//调用简单工厂来生产某种类型的咖啡
		SimpleCoffeeFactory* factory=new SimpleCoffeeFactory();
		Coffee* coffee=factory->createCoffee(type);
		//加配料
		coffee->addMilk();
		coffee->addSugar();
		
		return coffee;
	}

客户端

void main(String[] args) {
	//创建咖啡店类对象
	CoffeeStore* store=new CoffeeStore();
	Coffee* coffee=store->orderCoffee("latte");
	cout<<(coffee.getName());
}

优点:
封装了创建对象的过程,可以通过参数直接获取对象。把对象的创建和业务逻辑层分开,这样以后就避免了修改客户代码,如果要实现新产品直接修改工厂类,而不需要在原代码中修改,这样就降低了客户代码修改的可能性,更加容易扩展。
缺点:
增加新产品时还是需要修改工厂类的代码,违背了“开闭原则”。

3 工厂方法模式

针对上例中的缺点,使用工厂方法模式就可以完美的解决,完全遵循开闭原则。

3.1 概念

定义一个用于创建对象的接口,让子类决定实例化哪个产品类对象工厂方法使一个产品类的实例化延迟到其工厂的子类

3.2 结构:
工厂方法模式的主要角色:
抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法来创建产品。
具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。
具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。
img

创建咖啡的抽象工厂

class CoffeeFactory {
 	CoffeeFactory();
    virtual ~CoffeeFactory();
	//创建咖啡的抽象方法
	virtual Coffee* createCoffee();
};
/**
 * 拿铁咖啡工厂,专门生产拿铁咖啡
 * @author CharlieLiang
 *
 */
class LatteCoffeeFactory :public CoffeeFactory {
public:
    LatteCoffeeFactory();
    virtual ~LatteCoffeeFactory();
    Coffee* createCoffee() {
		// TODO Auto-generated method stub
		return new LatteCoffee();
	}
}
public class CoffeeStore {
 
private:
    CoffeeFactory* factory;
	
	void setFactory(CoffeeFactory* factory) {
		this.factory=factory;
	}
	//点咖啡的功能
public: 
    Coffee* orderCoffee() {
		Coffee* coffee=factory->createCoffee();
		//加配料
		coffee->addMilk();
		coffee->addSugar();
		return coffee;
	}
}
void main(String[] args) {
	//创建咖啡店对象
	CoffeeStore* store=new CoffeeStore();
		
	//创建对象
	CoffeeFactory* factory=new AmericanCoffeeFactory();
	store->setFactory(factory);
		
	//点咖啡
	Coffee* coffee=store->orderCoffee();
	cout<<(coffee->getName());
	}
}

从以上的编写的代码可以看到,要增加产品类时也要相应地增加工厂类不需要修改工厂类的代码了,这样就解决了简单工厂模式的缺点。

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

优点:

  • 用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程;
  • 系统增加新的产品时只需要添加具体产品类对应的具体工厂类,无须对原工厂进行任何修改,满足开闭原则;

缺点:

  • 每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度。

4.抽象工厂模式

4.1引入:

工厂方法模式只考虑生产同等级的产品,但是在现实生活中许多工厂是综合型的工厂,能生产多等级(种类) 的产品,如电器厂既生产电视机又生产洗衣机或空调,大学既有软件专业又有生物专业等。

抽象工厂模式将考虑多等级产品的生产,将同一个具体工厂所生产的位于不同等级的一组产品称为一个产品族,下图所示横轴是产品等级,也就是同一类产品;纵轴是产品族,也就是同一品牌的产品,同一品牌的产品产自同一个工厂。
img

**4.2 概念:**抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品。例如生产咖啡和蛋糕。

4.3 角色:

抽象工厂模式的主要角色如下:

抽象工厂(Abstract Factory):提供了创建产品的接口,它包含多个创建产品的方法,可以创建多个不同等级的产品。
具体工厂(Concrete Factory):主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。
抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它 同具体工厂之间是多对一的关系。

4.4实现:

现咖啡店业务发生改变,不仅要生产咖啡还要生产甜点,如提拉米苏、抹茶慕斯等,要是按照工厂方法模式,需要定义提拉米苏类、抹茶慕斯类、提拉米苏工厂、抹茶慕斯工厂、甜点工厂类,很容易发生类爆炸情况。其中拿铁咖啡、美式咖啡是一个产品等级,都是咖啡;提拉米苏、抹茶慕斯也是一个产品等级;拿铁咖啡和提拉米苏是同一产品族(也就是都属于意大利风味),美式咖啡和抹茶慕斯是同一产品族(也就是都属于美式风味)。所以这个案例可以使用抽象工厂模式实现。类图如下:
img
抽象产品:咖啡

/**
 * 咖啡类
 * @author CharlieLiang
 *
 */
class Coffee {
public:
    Coffee();
    virtual ~Coffee();
    virtual string getName();
	//加糖
	void addSugar() {
		cout<<("加糖");
	}
	//加奶
	void addMilk() {
		cout<<("加奶");
	}
}

具体产品:美式咖啡

class AmericanCoffee :public Coffee{
public:
    string getName() {
		// TODO Auto-generated method stub
		return "美式咖啡";
	}
};

具体产品:拿铁咖啡

class LatteCoffee :public Coffee{
public:
    string getName() {
		// TODO Auto-generated method stub
		return "拿铁咖啡";
	}
};

抽象产品:甜品

/**
 * 甜品抽象类
 * @author CharlieLiang
 *
 */
class Dessert {
public:
	Coffee();
    virtual ~Coffee();
	virtual void show();
}

具体产品:提拉米苏

class Trimisu :public Dessert{
public:
    void show() {
		// TODO Auto-generated method stub
		cout<<("提拉米苏");
	}
};

具体产品:抹茶慕斯

class MatchaMouse :public Dessert{
public:
    void show() {
		// TODO Auto-generated method stub
		cout<<("抹茶慕斯");
	}
}

抽象工厂:

class DessertFactory {
public:
    DessertFactory();
    virtual ~DessertFactory();
	//生产咖啡
	virtual Coffee* createCoffee();
	//生产甜品
	virtual Dessert* createDessert();
}

具体工厂:意大利风味工厂,生产提拉米苏, 生产拿铁咖啡

/**
 * 意大利风味工厂
 * 生产提拉米苏
 * 生产拿铁咖啡
 * @author CharlieLiang
 *
 */
class ItalyDessertFactory :public DessertFactory{
public:
    Coffee* createCoffee() {
		// TODO Auto-generated method stub
		return new LatteCoffee();
	}
	Dessert* createDessert() {
		// TODO Auto-generated method stub
		return new Trimisu();
	}
}

具体工厂:美式风味工厂 :生产美式咖啡、抹茶慕斯

/**
 * 美式风味工厂
 * 生产美式咖啡和抹茶慕斯
 * @author CharlieLiang
 *
 */
public class AmericanDessertFactory :public DessertFactory{
public:
    Coffee* createCoffee() {
		// TODO Auto-generated method stub
		return new AmericanCoffee();
	}
	Dessert* createDessert() {
		// TODO Auto-generated method stub
		return new MatchaMouse();
	}
}

抽象工厂模式客户端演示

/**
 * 抽象工厂客户端演示
 * @author CharlieLiang
 *
 */
void main(String[] args) {
		//创建意大利风味工厂
	DessertFactory factory=new ItalyDessertFactory();
		
	//获取拿铁咖啡和提拉米苏
	Coffee coffee=factory.createCoffee();
	Dessert trimisu=factory.createDessert();
		
	cout<<(coffee.getName());
	trimisu.show();
	}
}

如果要加同一个产品族的话,只需要**再加一个对应的工厂类(法国风味工厂)**即可,不需要修改其他的类。

4.5优缺点:

优点:

当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。

缺点:

当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改。

4.6:使用场景

4.2.4.4 使用场景

当需要创建的对象是一系列相互关联或相互依赖的产品族时,如电器工厂中的电视机、洗衣机、空调等。
系统中有多个产品族,但每次只使用其中的某一族产品。如有人只喜欢穿某一个品牌的衣服和鞋。
系统中提供了产品的类库,且所有产品的接口相同,客户端不依赖产品实例的创建细节和内部结构。
如:输入法换皮肤,一整套一起换。生成不同操作系统的程序。

5.说一说工厂模式

1)工厂模式的优点

对象都需要创建,如果创建的时候直接new该对象,就会对该对象耦合严重,假如我们要更换对象所有new对象的地方都需要修改一遍,这显然违背了软件设计的开闭原则**。如果我们使用工厂来生产对象,我们就只和工厂打交道就可以了,彻底和对象解耦,如果要更换对象,直接在工厂里更换该对象即可,达到了与对象解耦的目的**;所以说,工厂模式最大的优点就是:解耦

2)简单工厂角色
  • 抽象产品 :定义产品的接口
  • 具体产品 :继承抽象产品的子类(美式咖啡或拿铁咖啡)并对接口函数进行重写
  • 具体工厂 :**通过传入参数的不同来创建不同的产品。
  • 缺点:
    增加新产品时还是需要修改工厂类的代码,违背了“开闭原则”。
3)工厂方法模式的主要角色:
  • 抽象产品(Product):定义了产品的接口
  • 具体产品(ConcreteProduct):继承抽象产品的子类(美式咖啡或拿铁咖啡)并对接口函数进行重写
    • 抽象工厂(Abstract Factory):提供了创建产品的接口
  • 具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。如果要加同一个产品族的话,只需要**再加一个对应的工厂类(法国风味工厂)**即可,不需要修改其他的类。

缺点:

  • 每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度。
4)抽象工厂模式

工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品。例如生产咖啡和蛋糕。
例如现在不仅要生产咖啡,还需要生产蛋糕,那这个工厂中不仅需要生成不同种类的咖啡,还需要生产不同种类的蛋糕。
缺点
当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改。

3.原型模式

1.概述

用一个已经创建的实例为原型,通过复制该原型对象创建一个和原型对象相同的新对象

2.结构

1.(Prototype)抽象原型类

抽象原型类是定义具有克隆自己的方法的接口,是所有具体原型类的公共父类。

2.ConcretePrototype(具体原型类)

具体原型类实现具体的克隆方法,在克隆方法中返回自己的一个克隆对象。

3.Client (客户类)
客户类让一个原型克隆自身,从而创建一个新的对象。在客户类中只需要直接实例化或通过工厂方法等创建一个对象,再通过调用该对象的克隆方法复制得到多个相同的对象。

原型模式分类:
原型模式克隆对象时,根据其成员对象是否也在克隆,原型模式可以分为两种形式,深克隆和浅克隆。

img

3.实现

(1)浅克隆
浅复制是对值类型的成员变量进行复制, 对引用类型的变量只是对引用进行复制, 实际上两个对象还是指向的同一实例

不定义拷贝构造函数,类会自己生成默认拷贝构造函数,默认拷贝构造函数是浅拷贝

#include <iostream>
using namespace std;
class test {
public:
    test(int num) {
        test_ptr_ = new int(num);
    }
    ~test() {
        if (test_ptr_ != nullptr) {
            cout << "delete test" << endl;
            delete test_ptr_;
        }
 
        test_ptr_ = nullptr;
    }
private:
    int *test_ptr_;
};
int main()
{
    test t1(10);
    test t2(t1);
    return 0;
}

在一个类中,如果定义有指针成员变量,该指针指向new出来的堆内存。在用一个已经存在的对象初始化一个新对象时调用拷贝构造函数,如果拷贝构造函数中只是将已有对象的指针成员赋值给新对象的指针成员,那么就叫浅拷贝,浅拷贝以后两个对象中的指针成员指向同一个堆内存,在对象析构时,同一块堆内存会释放两次,堆内存释放两次程序会发生core dump,马上崩溃。正确的做法应该是在拷贝构造函数中为新对象开辟一段新内存,将已有对象堆内存里面的数据拷贝到新开辟的内存,这叫做深拷贝,深拷贝以后两个对象中的指针成员指向不同的内存,自己负责释放自己的堆内存。

**c++浅拷贝程序崩溃的原因:**创建t2的时候程序必然会去调用拷贝构造函数,这时候拷贝构造仅仅只是完成了值拷贝,导致两个指针指向了同一块内存区域。随着程序的运行结束,又去调用析构函数,先是t2去调用析构函数,释放了它指向的内存区域,接着t1又去调用析构函数,这时候析构函数企图释放一块已经被释放的内存区域,程序将会崩溃。

(2)深克隆
深复制不仅值类型的成员变量进行复制, 还对引用类型的成员变量申请存储空间, 让他成为一个新对象

class test {
public:
    test(int num) {
        test_ptr_ = new int(num);
    }
    test(const test& right) {
      //  test_ptr_ = new int(*right.test_ptr_);
        test_ptr_ = new int;//为指针分配内存
        *test_ptr_ = *right.test_ptr_;//拷贝值
    }
    ~test() {
        if (test_ptr_ != nullptr) {
            cout << "delete test" << endl;
            delete test_ptr_;
        }
 
        test_ptr_ = nullptr;
    }
private:
    int* test_ptr_;
};
int main()
{
 
    test t1(10);
    test t2(t1);
	return 0;
}

1.3 优缺点
优点
(1)当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程
(2)可以动态的减少或增加产品类。
缺点
(1)需要为每一个类配备一个克隆方法,而且对这个克隆方法需要对类的功能进行通盘考虑。
(2)在实现克隆时,需要编写较为复杂的代码。

1.4 适用场景
(1)创建新对象成本较大(如初始化需要较长的时间),新对象可通过原型模式对已有对象进行复制来获得。
1.5 应用举例
原型模式提供了一个通过已存在对象进行新对象创建的接口(Clone),Clone()实现和具体的实现语言相关,在 C++中我们将通过拷贝构造函数实现之。
img

#include <iostream> 
using namespace std; 
/*抽象原型类*/
class Student
{
protected:
    int id;
    char name[10];
public:
    virtual Student* Clone() = 0;
    virtual void Show() = 0;
};
/*具体原型类*/
class StudentTypeA :public Student
{
public:
    StudentTypeA(const char* name_input)
    {
        strcpy(name, name_input);
        this->id = 0;
        cout << "Construction....." << endl;
    }
    StudentTypeA(const StudentTypeA& other)
    {
        cout << "Copy Construction..." << endl;
        this->id = other.id;
        this->id++;
        strcpy(this->name, other.name);
    }
    virtual StudentTypeA* Clone()
    {
        StudentTypeA* tmp = new StudentTypeA(*this);
        return tmp;
    }
    void Show()
    {
        cout << "Student id == " << id << " name == " << name << endl;
    }
    ~StudentTypeA()
    {
        cout << "Deconstruction StudentTypeA" << endl;
    }
};
int main() 
{ 
  Student *student1 = new StudentTypeA("xiaoming"); 
  Student *student2 = student1->Clone(); 
  Student *student3 = student2->Clone();   
    
  student1->Show(); 
  student2->Show(); 
  student3->Show();   
  return 0; 
} 

总结:

其实这个设计模式比较简单,我们总结一下具体操作步骤。

1、声明一个抽象基类,并定义clone()函数为纯虚函数。

2、实例化各个子类,并且实现复制构造函数,并实现clone()函数

原型模式通过复制原型(原型)而获得新对象创建的功能,这里原型本身就是"对象工厂"(因为能够生产对象),实际上原型模式和 Builder 模式、AbstractFactory 模式都是通过一个类(对象实例)来专门负责对象的创建工作(工厂对象),它们之间的区别是: Builder 模式重在复杂对象的一步步创建(并不直接返回对象),AbstractFactory 模式重在产生多个相互依赖类的对象,而原型模式重在从自身复制自己创建新类。

1.说一说原型模式

用一个已经创建的实例为原型,通过复制该原型对象创建一个和原型对象相同的新对象

(Prototype)抽象原型类
ConcretePrototype(具体原型类)

原型模式分类:
原型模式克隆对象时,根据其成员对象是否也在克隆,原型模式可以分为两种形式,深克隆和浅克隆。

(1)浅克隆
浅复制是对值类型的成员变量进行复制, 对引用类型的变量只是对引用进行复制, 实际上两个对象还是指向的同一实例
浅拷贝以后两个对象中的指针成员指向同一个堆内存,在对象析构时,同一块堆内存会释放两次,堆内存释放两次程序会发生core dump

(2)深克隆
深复制不仅值类型的成员变量进行复制, 还对引用类型的成员变量申请存储空间, 让他成为一个新对象

原型模式提供了一个通过已存在对象进行新对象创建的接口(Clone),Clone()实现和具体的实现语言相关,在 C++中我们将通过拷贝构造函数实现之。

4.建造者模式

1.概述

将一个复杂对象的构建表示分离,使得同样的构建过程可以创建不同的表示。

1.分离了部件的构造(由Builder来负责)装配(由Director负责)。 从而可以构造出复杂的对象。这个模式适用于:某个对象的构建过程复杂的情况。
2.由于实现了构建和装配的解耦。不同的构建器(builder),,相同的装配,也可以做出不同的对象;相同的构建器,不同的装配顺序也可以做出不同的对象。也就是实现了构建算法、装配算法的解耦,实现了更好的复用。
3.建造者模式可以将部件和其组装过程分开,一步一步创建一个复杂的对象。用户只需要指定复杂对象的类型就可以得到该对象,而无须知道其内部的具体构造细节。
2.结构
建造者(Builder)模式包含如下角色:
抽象建造者类(Builder):这个接口规定要实现复杂对象的那些部分的创建,并不涉及具体的部件对象的创建。
具体建造者类(ConcreteBuilder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。在构造过程完成后,提供产品的实例。
产品类(Product):要创建的复杂对象。
指挥者类(Director)调用具体建造者来创建复杂对象的各个部分,在指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建。

2.示例
使用建造者模式,完成下述任务:计算机组装工厂可以将CPU、内存、硬盘、主机等硬件设备组装在一起构成计算机,计算机的类型可以是笔记本,也可以是台式机。

img

产品类

//产品类——Computer类
//要创建的复杂对象
class Computer{
private:
    string CPU;
    string RAM;
    string Hard;
    string Host;
public:
    string getCPU(){
        return CPU;
    }
    string getRAM(){
        return RAM;
    }
    string getHard(){
        return Hard;
    }
    string getHost(){
        return Host;
    }
    void setCPU(string cpu){
        CPU=cpu;
    }
    void setRAM(string ram){
        RAM=ram;
    }
    void setHard(string hard){
        Hard=hard;
    }
    void setHost(string host){
        Host=host;
    }
};

抽象建造者类——ComputerBuilder类

class ComputerBuilder{
protected:
    Computer computer;
public:
    virtual void buildCPU()=0;
    virtual void buildRAM()=0;
    virtual void buildHard()=0;
    virtual void buildHost()=0;
    Computer getComputer(){
        return computer;
    }
};

具体建造者类——LaptopComputerBuilder类

class LaptopComputerBuilder:public ComputerBuilder{
public:
    void buildCPU() {
        computer.setCPU("安装笔记本CPU");
    }
    void buildRAM() {
        computer.setRAM("安装笔记本内存");
    }
    void buildHard() {
        computer.setHard("安装笔记本硬盘");
    }
    void buildHost() {
        computer.setHost("安装笔记本主机");
    }
};

具体建造者类——DesktopComputerBuilder类

class DesktopComputerBuilder:public ComputerBuilder{
public:
    void buildCPU() {
        computer.setCPU("安装台式机CPU");
    }
    void buildRAM() {
        computer.setRAM("安装台式机内存");
    }
    void buildHard() {
        computer.setHard("安装台式机硬盘");
    }
    void buildHost() {
        computer.setHost("安装台式机主机");
    }
};

指挥者类——Director类

class Director{
private:
    //聚合。
    ComputerBuilder* cb;
public:
    void setComputerBuilder(ComputerBuilder *c){
        cb=c;
    }
    //构造电脑功能
    Computer construct(){
        cb->buildCPU();
        cb->buildRAM();
        cb->buildHard();
        cb->buildHost();
        return cb->getComputer();
    }
};

测试

int main(){
    ComputerBuilder* cb=NULL;
    int i;
    cout<<"请选择:1.笔记本 2、台式机"<<endl;
    cin>>i;
    //构造
    if(i==1){
        cb=new LaptopComputerBuilder();
    }else if(i==2){
        cb=new DesktopComputerBuilder();
    }else{
        cout<<"输入无效!";
    }
    //装配
    Director* dir=new Director();
    dir->setComputerBuilder(cb);
    Computer computer=dir->construct();
    cout<<"计算机组成:"<<endl;
    cout<<computer.getCPU()<<endl;
    cout<<computer.getRAM()<<endl;
    cout<<computer.getHard()<<endl;
    cout<<computer.getHost()<<endl;
    delete cb;
    delete dir;
    return 0;
}

抽象ComputerBuilder聚合在Direactor类中,通过多态将DesktopComputerBuilder或LaptopComputerBuilder赋值给ComputerBuilder。其中,DesktopComputerBuilder和LaptopComputerBuilder中构建对象的实现方法不同。

4.优缺点:
优点: 建造者模式的封装性很好。使用建造者模式可以有效的封装变化,在使用建造者模式的场景中,一 般产品类和建造者类是比较稳定的,因此,将主要的业务逻辑封装在指挥者类中对整体而言可以取 得比较好的稳定性。 在建造者模式中,客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得 相同的创建过程可以创建不同的产品对象。 可以更加精细地控制产品的创建过程 。将复杂产品的创建步骤分解在不同的方法中,使得创建过 程更加清晰,也更方便使用程序来控制创建过程。 建造者模式很容易进行扩展。如果有新的需求,通过实现一个新的建造者(Builder)类就可以完成,基本上不 用修改之前已经测试通过的代码,因此也就不会对原有功能引入风险。符合开闭原则。

缺点: 造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适 合使用建造者模式,因此其使用范围受到一定的限制。

5.使用场景
建造者(Builder)模式创建的是复杂对象,其产品的各个部分经常面临着剧烈的变化,但将它们组合 在一起的算法却相对稳定,所以它通常在以下场合使用。

创建的对象较复杂,由多个部件构成,各部件面临着复杂的变化,但构件间的建造顺序是稳定的。 创建复杂对象的算法独立于该对象的组成部分以及它们的装配方式,即产品的构建过程和最终的表 示是独立的。

二、创建者模式和工厂模式的对比
Factory模式中:
有一个抽象的工厂
实现一个具体的工厂 - 汽车工厂
工厂生产的汽车A,得到汽车产品A
工厂生产汽车B,得到汽车产品B
实现了购买者和生产线的隔离,强调的是结果

Builder模式
引擎工厂生产引擎产品,得到汽车的部件A
轮胎工厂生产轮子产品,得到汽车部件B
底盘工厂生产车身产品,得到汽车部件C
将这些部件放到一起,形成刚好能够组装成一辆汽车的整体
这样做,目的是为了实现复杂对象生产线和其部件的解耦。强调的是过程

两者的区别在于以下几种情况:

工厂模式不考虑对象的组装过程,而直接生成一个我想要的对象。

Builder模式先一个个的创建对象的每一个部件,再统一组装成一个对象

工厂模式所解决的问题是,工厂生产产品

而Builder模式解决的是工厂控制产品 生成器组装各个部件的过程,然后从产品生成器中得到产品。

工厂方法模式VS建造者模式
工厂方法模式注重的是整体对象的创建方式;而建造者模式注重的是部件构建的过程,意在通过一步一步地精确构造创建出一个复杂的对象。

我们举个简单例子来说明两者的差异,如要制造一个超人,如果使用工厂方法模式,直接产生出来的就是一个力大无穷、能够飞翔、内裤外穿的超人;而如果使用建造者模式,则需要组装手、头、脚、躯干等部分,然后再把内裤外穿,于是一个超人就诞生了。

抽象工厂模式VS建造者模式
抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式则是不需要关心构建过程,只关心什么产品由什么工厂生产即可。

建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品。

如果将抽象工厂模式看成汽车配件生产工厂,生产一个产品族的产品,那么建造者模式就是一个汽车组装工厂,通过对部件的组装可以返回一辆完整的汽车。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java设计模式是一组经过实践验证的面向对象设计原则和模式,可以帮助开发人员解决常见的软件设计问题。下面是常见的23种设计模式: 1. 创建型模式(Creational Patterns): - 工厂方法模式(Factory Method Pattern) - 抽象工厂模式(Abstract Factory Pattern) - 单例模式(Singleton Pattern) - 原型模式(Prototype Pattern) - 建造者模式(Builder Pattern) 2. 结构型模式(Structural Patterns): - 适配器模式(Adapter Pattern) - 桥接模式(Bridge Pattern) - 组合模式(Composite Pattern) - 装饰器模式(Decorator Pattern) - 外观模式(Facade Pattern) - 享元模式(Flyweight Pattern) - 代理模式(Proxy Pattern) 3. 行为型模式(Behavioral Patterns): - 责任链模式(Chain of Responsibility Pattern) - 命令模式(Command Pattern) - 解释器模式(Interpreter Pattern) - 迭代器模式(Iterator Pattern) - 中介者模式(Mediator Pattern) - 备忘录模式(Memento Pattern) - 观察者模式(Observer Pattern) - 状态模式(State Pattern) - 策略模式(Strategy Pattern) - 模板方法模式(Template Method Pattern) - 访问者模式(Visitor Pattern) 4. 并发型模式(Concurrency Patterns): - 保护性暂停模式(Guarded Suspension Pattern) - 生产者-消费者模式(Producer-Consumer Pattern) - 读写锁模式(Read-Write Lock Pattern) - 信号量模式(Semaphore Pattern) - 线程池模式(Thread Pool Pattern) 这些设计模式可以根据问题的特点和需求来选择使用,它们提供了一些可复用的解决方案,有助于开发高质量、可维护且易于扩展的软件系统。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值