Design Pattern (2) - Creational Pattern

Creational pattern refers to the way to create objects or obtain instances.

1. Factory mode

When writing some simple code, you may directly use new to create an object. However, when reading some projects with many functions and large scale, you may find that multiple classes inherit from the same base class. They have The same interface but implements different functions. They may be two systems that can replace each other (such as ACodec and CCodec in Android Media), or they may be multiple tools with different functions (such as different extractors in MediaExtractor), which are often used when creating such objects 工厂模式.

I think the factory pattern here is a broad concept. It refers to not hard coding when creating objects with the same characteristics (same base class). This will become difficult to maintain after the project becomes large. We need A dynamic creation method that can flexibly create specific classes based on conditions. The creation method here does not only refer to the following three specific factory design patterns, but may also be a conditional judgment function.

The implementation of the factory pattern mainly relies on the characteristics of polymorphism.

1.1. Simple factory mode

We can think that objects with the same characteristics (same base class) can be produced by a factory. According to different requirements, the factory can produce different object instances. This is the simple factory pattern.

Taking a simple calculator as an example, there are calculator types such as addition and subtraction. At this time, we can create a calculator factory and create corresponding calculator instances based on the passed symbols:

class Calculator {
    
    
public:
	virtual int calculate(int a, int b) = 0;
};

class AddCalculator : public Calculator {
    
    
public:
	int calculate(int a, int b) {
    
    
		return a + b;
	}
};

class SubCalculator : public Calculator {
    
    
public:
	int calculate(int a, int b) {
    
    
		return a - b;
	}
};

class CalculatorFactory {
    
    
public:
	static Calculator* createCalculator(const char c) {
    
    
		Calculator* cal = NULL;
		switch (c)
		{
    
    
		case '+':
			cal = new AddCalculator;
			break;
		case '-':
			cal = new SubCalculator;
			break;
		default:
			break;
		}
		return cal;
	}
};

int main() {
    
    
	Calculator* addCal = CalculatorFactory::createCalculator('+');
	printf("5 + 5 = %d\n", addCal->calculate(5, 5));
	Calculator* subCal = CalculatorFactory::createCalculator('-');
	printf("5 - 5 = %d\n", subCal->calculate(5, 5));
	delete addCal;
	delete subCal;
	return 0;
}

Please add image description

What are the benefits of using the simple factory pattern?

  • As long as the calculator type is specified for the factory, it can create the corresponding instance for us. The interface for creating the instance is unified. If you want to modify the object type later, you only need to modify the parameters passed to the factory;
  • If we want to add new calculator types later, we only need to modify the factory, and the usage method can still remain consistent.

1.2. Factory method pattern

A simple factory needs to specify the instance type to be created. 指定This action can also be regarded as a type of hard code. Is there a way to let the factory do it for us?Automatically select the type of instance createdWoolen cloth? The factory method pattern can help us complete automatic selection actions.

Let’s make some modifications to the simple calculator example above:

class Calculator {
    
    
public:
	virtual int calculate(int a, int b) = 0;
};

class AddCalculator : public Calculator {
    
    
public:
	int calculate(int a, int b) {
    
    
		return a + b;
	}
};

class SubCalculator : public Calculator {
    
    
public:
	int calculate(int a, int b) {
    
    
		return a - b;
	}
};

class CalFactory {
    
    
public:
	virtual Calculator* createCalculator() = 0;
};

class AddCalFactory : public CalFactory {
    
    
public:
	Calculator* createCalculator() {
    
    
		return new AddCalculator;
	}
};

class SubCalFactory : public CalFactory {
    
    
public:
	Calculator* createCalculator() {
    
    
		return new SubCalculator;
	}
};

int main() {
    
    

	CalFactory *addFactory = new AddCalFactory;
	Calculator* addCal = addFactory->createCalculator();
	printf("5 + 5 = %d\n", addCal->calculate(5, 5));
	CalFactory *subFactory = new SubCalFactory;
	Calculator* subCal = subFactory->createCalculator();
	printf("5 - 5 = %d\n", subCal->calculate(5, 5));
	delete addCal;
	delete subCal;
	delete addFactory;
	delete subFactory;
	return 0;
}

Please add image description
Each Calculator class in the example has its corresponding CalFactory factory. If you want to create the required type, you only need to instantiate the corresponding factory.

The factory method pattern we see in other blogs on the Internet usually ends here. There will be questions here. How is automatic selection reflected? What are the advantages of the factory method pattern compared to the simple factory?

Next, take Android MediaPlayerFactory as an example to see how the factory method pattern is used in actual combat:

There is a method in MediaPlayerFactory getPlayerType, which will traverse each specific IFactory, call their scoreFactory method to get the optimal score, and get the optimal factory type; then call the MediaPlayerFactory method to createPlayercreate an instance with the automatically selected factory (of course the score selection and Instantiated objects can be put into a method).

Please add image description

1.3. Abstract factory pattern

Abstract factories are similar to factory methods. Here are just simple examples:

class Pencil {
    
    
public:
	Pencil(const char* factory) : mFactory(factory) {
    
    }
	virtual void write() = 0;
protected:
	const char* mFactory;
};

class RedPencil : public Pencil {
    
    
public:
	RedPencil(const char* factory) : Pencil(factory) {
    
    }
	void write() {
    
    
		printf("%s", mFactory);
		printf(" Red Pencil write\n");
	}
};

class BlackPencil : public Pencil {
    
    
public:
	BlackPencil(const char* factory) : Pencil(factory) {
    
    }
	void write() {
    
    
		printf("%s", mFactory);
		printf(" Black Pencil write\n");
	}
};

class Pen {
    
    
public:
	Pen(const char* factory) : mFactory(factory) {
    
    }
	virtual void write() = 0;
protected:
	const char* mFactory;
};

class RedPen : public Pen {
    
    
public:
	RedPen(const char* factory) : Pen(factory) {
    
    }
	void write() {
    
    
		printf("%s", mFactory);
		printf(" Red Pen write\n");
	}
};

class BlackPen : public Pen {
    
    
public:
	BlackPen(const char* factory) : Pen(factory) {
    
    }
	void write() {
    
    
		printf("%s", mFactory);
		printf(" Black Pen write\n");
	}
};


class Factory {
    
    
public:
	virtual Pencil* createPencil(int catgory) = 0;
	virtual Pen* createPen(int catgory) = 0;
};

class AppleFactory : public Factory {
    
    
public:
	virtual Pencil* createPencil(int catgory) {
    
    
		Pencil* p = NULL;
		switch (catgory)
		{
    
    
		case 1:
			p = new RedPencil("AppleFactory");
			break;
		case 2:
			p = new BlackPencil("AppleFactory");
			break;
		default:
			break;
		}
		return p;
	}
	virtual Pen* createPen(int catgory) {
    
    
		Pen* p = NULL;
		switch (catgory)
		{
    
    
		case 1:
			p = new RedPen("AppleFactory");
			break;
		case 2:
			p = new BlackPen("AppleFactory");
			break;
		default:
			break;
		}
		return p;
	}
};

class OrangeFactory : public Factory {
    
    
public:
	virtual Pencil* createPencil(int catgory) {
    
    
		Pencil* p = NULL;
		switch (catgory)
		{
    
    
		case 1:
			p = new RedPencil("OrangeFactory");
			break;
		case 2:
			p = new BlackPencil("OrangeFactory");
			break;
		default:
			break;
		}
		return p;
	}
	virtual Pen* createPen(int catgory) {
    
    
		Pen* p = NULL;
		switch (catgory)
		{
    
    
		case 1:
			p = new RedPen("OrangeFactory");
			break;
		case 2:
			p = new BlackPen("OrangeFactory");
			break;
		default:
			break;
		}
		return p;
	}
};

static Factory* createFactory(const char* factoryName) {
    
    
	Factory* factory = NULL;
	if (!memcmp(factoryName, "Apple", 5))
	{
    
    
		factory = new AppleFactory;
	}
	else if (!memcmp(factoryName, "Orange", 6)) {
    
    
		factory = new OrangeFactory;
	}
	return factory;
}

int main() {
    
    
	Factory* AppFac = createFactory("Apple");
	Pen* AppPen = AppFac->createPen(1);
	AppPen->write();
	Pencil* AppPencil = AppFac->createPencil(1);
	AppPencil->write();

	Factory* OraFac = createFactory("Orange");
	Pen* OraPen = OraFac->createPen(2);
	OraPen->write();
	Pencil* OraPencil = OraFac->createPencil(2);
	OraPencil->write();

	delete AppFac;
	delete AppPen;
	delete AppPencil;
	delete OraFac;
	delete OraPen;
	delete OraPencil;
}

2. Builder mode

class Builder;
class Computer {
    
    
protected:
	friend class Builder;
	void setBrand(std::string brand)	{
    
     mBrand = brand; }
	void setCpu(std::string cpu)		{
    
     mCpu = cpu; }
	void setGpu(std::string gpu)		{
    
     mGpu = gpu; }
	void setPrice(int price)			{
    
     mPrice = price; }
public:
	void print() {
    
    
		printf("%s \t %s \t %s \t %d\n", mBrand.c_str(), mCpu.c_str(), mGpu.c_str(), mPrice);
	}
private:
	std::string mBrand;
	std::string mCpu;
	std::string mGpu;
	int			mPrice;
};

class GrapeComputer : public Computer {
    
    
public:
	GrapeComputer() {
    
    
		setBrand("Grape");
		setGpu("RTX4080");
	}
};

class OrangeComputer : public Computer {
    
    
public:
	OrangeComputer() {
    
    
		setBrand("Orange");
	}
};

class Director;
class Builder {
    
    
protected:
	friend class Director;
	virtual void buildCpu(std::string cpu) {
    
     mComputer->setCpu(cpu); };
	virtual void buildGpu(std::string gpu) {
    
     mComputer->setGpu(gpu); };
	virtual void buildPrice(int price) {
    
     mComputer->setPrice(price); };
public:
	virtual Computer* build() {
    
    
		return mComputer;
	}
protected:
	Computer *mComputer;
};

class GrapeBuilder : public Builder
{
    
    
public:
	GrapeBuilder() {
    
    
		mComputer = new GrapeComputer();
	}
	virtual void buildGpu(std::string gpu) override {
    
     };
};

class OrangeBuilder : public Builder
{
    
    
public:
	OrangeBuilder() {
    
    
		mComputer = new OrangeComputer();
	}
};


class Director {
    
    
public:
	Director(Builder *builder) {
    
     mBuilder = builder; }
	Computer * construct(std::string cpu, std::string gpu, int price) {
    
    
		mBuilder->buildCpu(cpu);
		mBuilder->buildGpu(gpu);
		mBuilder->buildPrice(price);
		return mBuilder->build();
	}
private:
	Builder *mBuilder;
};

int main() {
    
    
	Builder *grapeBuilder = new GrapeBuilder;
	Director *directorA = new Director(grapeBuilder);
	Computer *grapeComputer = directorA->construct("core i7 13700k", "standard", 10999);
	grapeComputer->print();
	delete grapeBuilder;
	delete directorA;
	delete grapeComputer;

	Builder *orangeBuilder = new OrangeBuilder;
	Director *directorB = new Director(orangeBuilder);
	Computer *orangeComputer = directorB->construct("core i5 12400f", "RTX3060", 5999);
	orangeComputer->print();
	delete orangeBuilder;
	delete directorB;
	delete orangeComputer;
}

The above is an example I wrote after reading online content. The example mainly consists of Director and Builder. My understanding is that Director is the caller, and different Builders are used to handle different transactions/create different objects. Quoting online analysis:

In the client code, there is no need to care about the specific assembly process of the product object. You only need to determine the type of the specific builder. The builder pattern separates the construction of complex objects from the performance of the objects, so that the same construction process can create perform differently.

I have not seen the use of the builder pattern that completely matches the above example in actual combat. I have only seen similar usage in
Android MediaCodecList.cpp .

When instantiating MediaCodecList, GetBuilders will be called first to obtain Codec2InfoBuilder and OmxInfoBuilder instances, and then their buildMediaCodecList methods will be called traversingly to write all the information to MediaCodecListWriter. Although there is no specific Director in this example, and no specific object is returned, there are only different Builders, but I think it is consistent with the idea of ​​the builder pattern.

3. Singleton mode

The singleton mode is relatively simple. We can use the singleton mode when we do not want to repeatedly build an object. For example, DataSourceFactory.cpp and the above-mentioned MediaCodecList.cpp are typical singleton modes:

struct MediaCodecList : public BnMediaCodecList {
    
    
	static sp<IMediaCodecList> getInstance();
private:
    static sp<IMediaCodecList> sCodecList;
    MediaCodecList(std::vector<MediaCodecListBuilderBase*> builders);
    MediaCodecList(const MediaCodecList&) = delete;
    MediaCodecList& operator=(const MediaCodecList&) = delete;
}

The usage scenarios of singleton mode encountered so far are as follows:

  • Load and store information for query, using singleton mode to avoid multiple information loading (MediaCodecList)
  • Provides a series of methods for use, using singleton mode can reduce the overhead of object creation (DataSourceFactory).

The only thing to note is that you need to use a lock when obtaining an object instance to avoid calling getInstance at the same time and creating an object at the same time:

sp<DataSourceFactory> DataSourceFactory::getInstance() {
    
    
    Mutex::Autolock l(sInstanceLock);
    if (!sInstance) {
    
    
        sInstance = new DataSourceFactory();
    }
    return sInstance;
}

Guess you like

Origin blog.csdn.net/qq_41828351/article/details/132724052