设计模式之工厂模式

一、引言

马上找工作了,自己做了一份简历,发现很多知识都忘得一干二净,所以最近准备做一个关于设计模式方面的专题,想通过自己写一些文章加深对设计模式的理解,也欢迎大家来阅读,同时给予批评指正。

闲话少说,我们进入正题吧,设计模式主要的功能就是实现代码的可扩展性,在现在面向对象开发过程中应用已经非常广泛了,同时它也是一个高级程序员应该具有的基本素养,在面试环节会经常被问到,今天我们就来聊一聊设计模式中最著名的工厂模式吧!

二、工厂模式分类

1)简单工厂模式(Simple Factory

2)工厂方法模式(Factory Method

3)抽象工厂模式(Abstract Factory

这三个工厂模式由上到下抽象层次逐渐增加,代码的可扩展性也逐渐增加,但是我们不必完全抛弃前两种设计模式,具体看我们的业务需求。

三、场景布置

比如我们现在做一款坦克大战的游戏,游戏中自然就有一系列Tank的对象,游戏开始后,玩游戏的人控制着自己的tank打敌方的tank。

四、简单工厂

public class ChinaTank {
	
	public void shoot(){
		
		System.out.println("ChinaTank is shooting....");
	}
}
这是具体的一个ChinaTank类的实现,我们打算根据这个具体的Tank来作为主程序的Tank,游戏中有很多Tank
public class Client {

	/**
	 * @param args
	 */
	public static void main(String[] args) {

                ChinaTank t1 = new ChinaTank();
                ChinaTank t2 = new ChinaTank();
                ChinaTank t3 = new ChinaTank();
                t1.shoot();
                t2.shoot();
                t3.shoot();
	}
	
}
这是在利用简单工厂前我们的游戏主代码实现,程序启动后就能看到一个中国的Tank在shooting,那么我们遇到了什么问题呢?就功能来讲Client完全
没有任何问题,但是游戏者逐渐感到厌烦了,因为游戏中的Tank实在太单一,我们现在想在游戏中添加多个Tank的种类,现在应该怎么做呢?首先我
兴致盎然的在系统中写了一个AmericaTank的类
public class AmericaTank {
	
	public void shoot(){
		
		System.out.println("AmericaTank is shooting....");
	}
}
写完后我就想把这个AmericaTank类对象嵌入到Client中,以替换当前的ChinaTank类,可是我突然发现只有通过修改原来的源代码为
public class Client {

	/**
	 * @param args
	 */
	public static void main(String[] args) {

                AmericaTank t1 = new AmericaTank();
                AmericaTank t2 = new AmericaTank();
                AmericaTank t3 = new AmericaTank();
                
                t1.shoot();
                t2.shoot();
                t3.shoot();
    }
	
}


才可以实现替换游戏中的ChinaTank类对象,这显然不是我们想要的结果,因为我们不停的修改了3次Tank对象,有没有一种更好的方法来实现上述替换效果呢?
庆幸的是答案是肯定的,简单工厂可以做到,下面我们用简单工厂的方法来设计整个游戏。游戏中上面设计的两个ChinaTank类和AmericaTank类保留
,然后我们定义一个Tank接口(抽象类),同时让 ChinaTank和AmericaTank实现上述Tank接口,代码如下
public interface Tank {
	
	public void shoot();
}
public class ChinaTank implements Tank{
	
	public void shoot(){
		
		System.out.println("ChinaTank is shooting....");
	}
}
public class AmericaTank implements Tank{
	
	public void shoot(){
		
		System.out.println("AmericaTank is shooting....");
	}
}
我们都知道多态的好处,利用Tank接口,我们的客户代码可以只和Tank接口打交道,而不和具体的ChinaTank类或者AmericaTank类打交道,
public class Client {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		
		Tank t1 = new ChinaTank();
        Tank t2 = new ChinaTank();            
        Tank t3 = new ChinaTank();
        
        t1.shoot();
        t2.shoot();
        t3.shoot();
	}

}

可是现在的Client代码还是有ChinaTank类啊,还是没有办法遵循开闭原则(实现新功能的时候只添加新的代码,而不修改原来的代码),其实要做到 不修改代码很简单 ,我们只需要将代码中需要修改的部分去除掉,用不需要改变的代码来替换即可,上述Client中的ChinaTank正是我们的修改目标,
请注意:在我们平时的设计过程中,像 ChinaTank这样的统统都称为危险份子,就想MH370的机长,你不知道什么时候他会背叛人民,这样的危险
份子我们绝不允许它出现在代码中,就像这样的人我们绝对不允许他活在自由世界中一样。好了言归正传,我们需要用不可变的代码来代替Client中的
ChinaTank,怎么做到呢?下面我就添加了一个简单工厂类,它专门负责生产各种Tank
public class SimpleFactory {

	public Tank getTank(int kind){
		
		Tank t = null;
		
		switch(kind){
			case 1:
				t = new ChinaTank();
				break;
			case 2:
				t = new AmericaTank();	
				break;
		}
		
		return t;
	}

}
有了这个简单工厂后,我们就可以利用这个工厂类来代替原来的游戏Client中的ChinaTank了
public class Client {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		
		SimpleFactory sf = new SimpleFactory();
		Tank t1 = sf.getTank(1);
		Tank t2 = sf.getTank(1);
		Tank t3 = sf.getTank(1);
		
		t1.shoot();
		t2.shoot();
		t3.shoot();
			
	}

}

修改后的Client代码没有了具体的ChinaTank,但是游戏运行起来后,仍然呈现的是一辆ChinaTank在shooting,需要我们注意的是sf.getTank(1)中的1
可以由游戏者自己选择,游戏可以记录下这一选择并从配置文件中读取,或者通过其它途径传入getTank作为参数,也就是说我们完全避免了Client的代码改动

总结:通过简单工厂,我们解决了不用修改Client中的主代码部分即可切换游戏中tank的种类,当然这一切都基于游戏实现已经设计好了若干tank类,并 从Tank接口继承 ,简单工厂解决了对于Client而言的开闭原则的实现,即如果需要新增Tank,Client完全不用动,只需要改变SimpleFactory中的getTank即可,它实现了将Client中的可变部分抽取出来的效果,当我们新增其它Tank类的时候,可以很方便的找到对应的SimpleFactory,然后改变它,而不需要从庞大的工程中翻来找去哪些部位需要修改(因为除了Client要用Tank外,可能整个工程的其它部分也需要Tank)。

五、工厂方法

有了简单工厂我们就满足了吗?好吧到了给你一点打击的时候了,玩家重新玩了3天你新设计的游戏,要知道人是永远不知道满足的动物,它又开始厌倦了,为了迎合他的需求,程序员的老板发话了,说:“弯勇辉,限你两天时间在现有的游戏里添加新的一种Tank出来,要是完不成你知道你的后果的”。
他以为我会害怕他,但他没有想到的是我早已经在研究生的阶段练就了金刚不坏之身,对于这种针对我的言论我总是抱着呵呵的态度。现在我仍然是一名猿类,还没有进化完成,所以我只有听他的按时把工作完成。我打开原来的代码,仔细审视了一下,我发现用简单工厂的代码编写的Client端实在太好了,心里暗自高兴了一把,然后就开始动手了,我的打算是在游戏中再添加一个EnglishTank的类出来,然后把它嵌入整个游戏,首先我在工程里面添加了一个 EnglishTank
public class EnglishTank implements Tank{
	
	public void shoot(){
		
		System.out.println("EnglishTank is shooting....");
	}
}
好了,第一步已经完成了,现在要做的就是把它嵌入到游戏中去,因为前面使用了简单工厂,Client里的代码根本不用变,我只需要改变SimpleFactory就可以了
public class SimpleFactory {

	public Tank getTank(int kind){
		
		Tank t = null;
		
		switch(kind){
			case 1:
				t = new ChinaTank();
				break;
			case 2:
				t = new AmericaTank();	
				break;
			case 3:
				t = new EnglishTank();	
				break;
		}
		
		return t;
	}

}
好了,我的工作完成了,很简单吧,下次玩家玩游戏的时候他就可以有三种选择来决定游戏的tank到底是什么类型的。
高兴的过早了,我刚要把新完成的代码交给老板的时候,一个念头闪过我的脑海,因为我预测老板3天后还会再来找我,让我给游戏添加第4种tank出来
。我可不能总是在添加新的tank类后,再次回到SimpleFactory类中修改它的源码(刚刚你注意到了吗,我在将EnglishTank添加到游戏中的时候修改了
SimpleFactory的源码),对于SimpleFactory而言这违背了开闭原则。那么问题出在哪了呢?因为在简单工厂中我们只是简单的把生产对象的部分抽取了出来(程序中可能变化的部分)并放置到了一个我们起名为SimpleFactory的类中来,我们做的只是将可变的部分移了一个位置而已,对于整个程序而言,当有新的Tank出现的时候,我们还是避免不了对代码的修改。这貌似是一个不能避免的问题,但是我在这里还是要强调一下简单工厂的作用,它大大缩小了我们修改代码的范围,并一定程度上减少了代码修改的次数。

那有没有一种策略,在对Client端保持开闭原则的同时,也对工厂保持开闭原则,答案又是肯定的,下面我给出“工厂方法”策略
既然因为一个简单工厂生产各种Tank导致了该简单工厂的不稳定性,那我就让不同的工厂生产不同的Tank,这样问题不就解决了吗?是的,这就是所谓的“工厂方法”模式
关于Tank接口、ChinaTank、AmericaTank、EnglishTank的代码都不变,变化的是工厂的实现
首先我要定义一个抽象的工厂类,这种工厂不生产实际的Tank,它只是提供了生产Tank类型对象的方法
public interface TankFactory {
	
	public Tank getTank();
}
然后对应每种具体的tank,我都定义一个具体的工厂类
public class ChinaTankFactory implements TankFactory{

	@Override
	public Tank getTank() {
		
		return new ChinaTank();
	}

}
public class AmericaTankFactory implements TankFactory{

	@Override
	public Tank getTank() {
		
		return new AmericaTank();
	}

}
public class EnglishTankFactory implements TankFactory{

	@Override
	public Tank getTank() {
		
		return new EnglishTank();
	}

}
三种工厂分别用于生产三种不同的Tank,并且都实现了TankFactory接口,这样以来我如果想要在游戏使用ChinaTank,则Client程序可以这样写:
public class Client {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		
		TankFactory tf = new ChinaTankFactory();
		Tank t1 = tf.getTank();
		Tank t2 = tf.getTank();
		Tank t3 = tf.getTank();
		
		t1.shoot();
		t2.shoot();
		t3.shoot();
			
	}

}
看到了吗?这样的工厂方法其实已经很完美了,因为它做到了两点,一、我们的Client中代码不用变(ChinaTankFactory对象可由配置文件指定,然后通过反射机制生成 tf 对象),Client中产生的tank都符合面向抽象编程的理念。二、当系统要加入新的tank种类的时候,我们不需要变更系统的任何一行代码,我们需要做的只是在系统中添加新的Tank类和生成该Tank的TankFactory。

六、抽象工厂

工厂方法是完美的,但只是对于生产一系列相同种类的产品来说,例如上一节中所描述的ChinaTank和AmericaTank,它们共同实现了Tank接口,这样才保证了在替换Tank种类的情况下(我们的Client面向Tank接口编程)Client端代码保持不变。然而现实生活中我们往往需要生成一系列的对象,这些不同类型的对象共同的组成了我们的系统,比如Tank游戏中的坦克和子弹,我们就暂且叫它们为一个产品族,对与我们的系统而言他们是不可或缺的一部分。而tank本身又分为ChinaTank和AmericaTank两种类别,我们暂且假设游戏中的子弹也分为两种不同的类别:ChinaBullet和AmericaBullet。当我们选择中国元素为游戏的主元素时,游戏中应该同时有ChinaTank和ChinaBullet,当选择了美国元素的时候游戏中应该有AmericaTank和AmericaBullet,如果利用上述的工厂方法设计当前系统,系统会为每种不同的坦克和子弹设计各自的工厂,这样容易产生工厂爆炸(系统完成后会有很多很多的工厂类),虽然满足了开闭原则,但这并不理想,事实上我们应该针对每种不同的主题创建一个工厂,比如ChinaFactory,它专门负责生产ChinaTank和ChinaBullet,AmericaFactory它专门负责生产AmericaTank和AmericaBullet,同时为了保证Client代码的不变性,我们的各个主题工厂和各个产品都应该面向接口:下面看代码吧(最精彩的部分来啦)

首先我们定义一个抽象的工厂
public interface AbstractFactory {
	
	Tank getTank();
	Bullet getBullet();
}
然后各个不同的主题工厂生产不同主题的产品族
public class ChinaFactory implements AbstractFactory{
	
	@Override
	public Tank getTank() {
		return new ChinaTank();
	}
	
	@Override
	public Bullet getBullet() {
		return new ChinaBullet();
	}
}
public class AmericaFactory {
	
	@Override
	public Tank getTank() {
		return new AmericaTank();
	}
	
	@Override
	public Bullet getBullet() {
		return new AmericaBullet();
	}
}
对应的Client端代码如下,此时Client要生产若干坦克和子弹出来
public class Client {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		
		AbstractFactory af = new ChinaFactory();
		
		Tank t1 = af.getTank();
		Tank t2 = af.getTank();
		Tank t3 = af.getTank();
		
		Bullet b1 = af.getBullet();
		Bullet b2 = af.getBullet();
		Bullet b3 = af.getBullet();
			
	}

}
跟据上述方式,当有新的产品族要加入系统时,只需要设计对应的产品类,这生产这些产品类(产品家族)的具体工厂(记得实现AbstractFactory哦)即可。

好了,总结一下就是抽象工厂面向的问题是产品族的生产问题,它是对工厂方法的改进(工厂方法设计系统的话产生太多的工厂类)

七、总结工厂模式

简单工厂解决了对客户端代码的不变动(开闭原则),同时避免了批量生产产品的多次new具体产品问题,当需要扩展时工厂内方法需要变动
工厂方法解决了工厂的变动问题,对与Client和工厂来说都不需要变动
抽象工厂面向产品族的生产问题,是对工厂方法的扩展,避免了工厂方法中工厂类爆炸的问题。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
工厂模式是一种常见的创建型设计模式,用于创建对象,而不是通过直接调用构造函数来创建它们。工厂模式定义了一个接口,用于创建相关对象,但是让子类决定要实例化的类。在C++工厂模式可以通过以下步骤实现: 1. 创建一个抽象基类,该类定义了一个纯虚拟函数,该函数将返回一个指向基类的指针。这个基类就是我们的工厂接口。 ```c++ class Product { public: virtual ~Product() {} virtual void operation() = 0; }; ``` 2. 创建具体的产品类,它们继承自抽象基类,并实现了其纯虚拟函数。这些类就是我们的具体产品。 ```c++ class ConcreteProductA : public Product { public: void operation() override { /* 具体产品 A 的操作 */ } }; class ConcreteProductB : public Product { public: void operation() override { /* 具体产品 B 的操作 */ } }; ``` 3. 创建一个工厂类,该类实现了工厂接口,并根据需要创建具体的产品。这个工厂类就是我们的具体工厂。 ```c++ class Factory { public: virtual ~Factory() {} virtual std::unique_ptr<Product> createProduct() = 0; }; class ConcreteFactoryA : public Factory { public: std::unique_ptr<Product> createProduct() override { return std::make_unique<ConcreteProductA>(); } }; class ConcreteFactoryB : public Factory { public: std::unique_ptr<Product> createProduct() override { return std::make_unique<ConcreteProductB>(); } }; ``` 4. 在客户端代码使用具体工厂创建具体产品。 ```c++ int main() { std::unique_ptr<Factory> factory = std::make_unique<ConcreteFactoryA>(); std::unique_ptr<Product> product = factory->createProduct(); product->operation(); return 0; } ``` 这就是工厂模式的基本实现方式。通过这种方式,我们可以将对象的创建过程与客户端代码分离,从而更好地实现模块化和可扩展性。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值