设计模式-工厂模式(Factory Pattern)
本文由@呆代待殆原创,转载请注明出处。
工厂模式遵循的设计原则之一:找出代码中常变化的部分,并把这一部分分离出来。(Dependency Inversion Principle)
工厂模式简述
当我们需要在我们编写的代码里面实例化(将类实例化,在java中就是用到new的时候)特定的类时,我们的代码间的耦合性和维护的难度都会提高,因为我们很可能在将来某个时候为了增加另外的功能而去引入另外一个特定的类或者对现有的这个特定的类进行更改,这时我们就很有可能不得不去修改与之相关的存在依赖关系的其他源代码来适应这个变化,这样就严重违反了Open-Closed principle(允许扩展而拒绝修改),所以当需要在我们的代码中引入某些具体实例(concrete class,一个有特定用途且已被完全实现的对象)时,我们一般会使用工厂模式来创建这些实例,工厂模式一般分为工厂方法(Factory Method)和抽象工厂(Abstract Factory),我们一个一个介绍。
工厂模式的作用
在工厂设计模式中,我们一般将产生实例的代码集中到一个类或者一个方法中,形成一个简单工厂类或者一个工厂方法,因为混入具体实例的代码是非常容易变更的,当我们把它们集中起来后,有两个好处。
1,集中了易改变的代码,降低了维护代码的代价。
2,封装了易改变的代码,那么其他的代码就可以面向这个封装后的固定的接口编程,而不必面对某个特定的实例,降低了对象间的耦合。
工厂方法的定义与基本结构
定义:定义一个接口或者抽象类,将工厂方法定义为抽象,让他的子类去决定要实例化的类时哪一个,工厂方法让实例化推迟到子类。(看一下,有个印象,通过下面的举例来理解工厂模式更有效)
一张来自《Head First 设计模式》的结构图。
(图中的英文为书中对这个结构的解释与说明,下面的解释并不是对图中英文的直接翻译,而是博主自己的稍稍结合书中其他内容的总结)
Product(产品类):所有具体产品类的基类一般是一个抽象类或接口(也可以是个普通的类),工厂模式负责创建的类的基类,有了它,其他的代码都可以针对这个抽象来编程,而不用在意具体实例是什么。
ConcreteProduct(具体产品类):继承自Product,某个具体的产品,属于工厂方法返回的对象中的一种。
Creator(制作者类):所有具体制作者类的基类一般是一个抽象类或接口(也可以是个普通的类),包含了一个抽象的工厂方法,和其他每一个具体制作者都要调用的方法。
ConcreteCreator(具体制作者类):继承自Creator,某一类具体产品的制作者,实现工厂方法,在工厂方法里写明所以要创建的类,然后通过给工厂方法传入特定的参数,让其返回特定的实例。
不结合例子来理解这个模式是很困难的,所以上面的这些不太懂不要紧,接着看例子吧。
一个简单的实例
假设你在玩一个战争游戏,你需要造一些兵去攻打别人,那么你需要一个兵工厂来生产这些兵,但是我们的兵有很多种族啊,人族,妖族,兽族之类的,而且每一个种族还有很多种不同职业的兵种啊,比如魔法师,弓箭手,剑客什么的。对照上面的结构,我们的战士就是Product,妖族魔法师或者人族弓箭手就是ConcreteProduct,而我们的兵工厂就是Creator,某个种族的兵工厂就是ConcreteCreator。每一个兵工厂都要有creatorHero()这个工厂方法,但是,不同种族的兵工厂的createHero()方法造出来的兵种的种族是不一样的,而且一个createHero()可以造出不同职业的兵种。
我们可以一边讲实例,一边写代码,这样理解更快一点。
首先我们给出Product类,我们把这个类叫做Hero,Hero中给出了一个英雄的基本属性和必要的方法。
1 public abstract class Hero { 2 protected String name; 3 protected String armor; 4 protected String weapon; 5 void prepare(){ 6 System.out.println("身份:"+name); 7 System.out.println("穿上盔甲:"+armor); 8 System.out.println("拿上武器:"+weapon); 9 } 10 void practice(){ 11 System.out.println("接受训练"); 12 } 13 public String getName(){ 14 return name; 15 } 16 }
然后我们写一个魔法师类和一个弓箭手类,为了区分种族顺便写人族和妖族两个版本一共四个类。
1 public class HumanEnchanterHero extends Hero { 2 public HumanEnchanterHero(){ 3 name="人族魔法师"; 4 weapon="智杖"; 5 armor="法袍"; 6 } 7 } 8 9 public class SpriteEnchanterHero extends Hero { 10 public SpriteEnchanterHero(){ 11 name="妖族魔法师"; 12 weapon="智杖"; 13 armor="法袍"; 14 } 15 } 16 17 public class HumanArcherHero extends Hero { 18 public HumanArcherHero(){ 19 name="人族弓箭手"; 20 weapon="老弓"; 21 armor="轻盔"; 22 } 23 } 24 25 public class SpriteEnchanterHero extends Hero { 26 public SpriteEnchanterHero(){ 27 name="妖族魔法师"; 28 weapon="智杖"; 29 armor="法袍"; 30 } 31 }
然后我们来写creator类,也就是我们的兵工厂,我们就叫它Armoury。
1 public abstract class Armoury { 2 //我们造一个英雄调用的方法 3 public Hero giveHero(String type){ 4 Hero hero; 5 hero=createHero(type); 6 hero.prepare(); 7 hero.practice(); 8 return hero; 9 } 10 //这个就是我们的工厂方法 11 protected abstract Hero createHero(String type); 12 }
最后我们来写concreteCreator类,我们需要两个兵工厂一个人族的一个精灵族的。
这个是人族的兵工厂。
1 public class HumanArmoury extends Armoury { 2 //实现了工厂方法,根据type的内容返回特定的Hero,将具体实例封装在一个方法中,集中了易改变的代码,且当我们的客户代码调用的时候, 3 //客户只需要调用giveHero()方法,并只需了解他将获得一个Hero对象而不需要在意这些具体实例,实现了客户和具体实例间的解耦,而这是通过 4 //继承Armoury重写createHero()实现的,我们将在SpriteArmoury中实现另一类对象的创建,他将同样具有上述特性 5 @Override 6 protected Hero createHero(String type) { 7 if(type.equalsIgnoreCase("humanEnchanterHero")){ 8 return new HumanEnchanterHero(); 9 }else if(type.equalsIgnoreCase("humanArcherHero")){ 10 return new HumanArcherHero(); 11 }else{ 12 System.out.println("目前没有这个兵种\n"); 13 return null; 14 } 15 } 16 }
妖族的兵工厂。
1 public class SpriteArmoury extends Armoury { 2 @Override 3 protected Hero createHero(String type) { 4 if(type.equalsIgnoreCase("SpriteArcherHero")) 5 return new SpriteArcherHero(); 6 else if(type.equalsIgnoreCase("SpriteEnchanterHero")) 7 return new SpriteEnchanterHero(); 8 else { 9 System.out.println("目前没有这个兵种\n"); 10 return null; 11 } 12 } 13 }
最后,我们给出测试代码。
1 public class Test { 2 public static void main(String[] args){ 3 //这个就是客户程序,我们在这里创造战士的时候我们只需要了解我们需要一个Hero类型,然后调用相应工厂的giveHero()方法即可, 4 //givaHero()方法会为我们调用我们的工厂方法创建我们制定的英雄(具体产品类),并不需要了解具体类的存在。 5 Armoury humanarmoury=new HumanArmoury(); 6 Hero humanHero=humanarmoury.giveHero("HumanEnchanterHero"); 7 if(humanHero!=null) 8 System.out.println("我们得到了一个"+humanHero.getName()+"\n"); 9 10 Armoury spritearmoury=new SpriteArmoury(); 11 Hero spriteHero=spritearmoury.giveHero("spriteArcherHero"); 12 if(spriteHero!=null) 13 System.out.println("我们得到了一个"+spriteHero.getName()+"\n"); 14 } 15 }
输出如下:
结合例子和里面的注释,再看看上面的基本结构是不是对工厂方法有了基本理解?注意到这个结构里用了两次继承,ConcreteProduc继承自Product,ConcreteCreator继承自Creator,这是为了保证我们的编程是依赖于抽象(abstractions,指依赖抽象类和接口),而不是依赖实例(concrete classes)的,这是一个好的编程原则Dependency Inversion Principle(依赖倒置原则)。他的好处是显而易见的,假设人族和妖族结合诞生了一个新的种族,我们暂且叫她半(ren)妖族,那么怎么造半妖族的兵种呢?在这个架构下我们只要继承Creator写一个新的ConcreteCreator就好,这样我们就不用修改已经写好的代码,而又可以扩展原有的代码,这是另一个编程原则叫Open-Closed Principle(类应该支持扩展,而拒绝修改),是不是觉得很方便呢?你可以想一想在不写抽象基类的情况下,我们把所有的代码都放到一个类里是什么样子的,那个样子太耀眼,博主就不展示了= =
抽象工厂的定义与基本结构
定义: 提供一个接口或者抽象类来创造一类相关的具体实例对象,而且当客户代码调用是不需要了解具体实例的细节。
一张来自《Head First 设计模式》的结构图。
AbstractFactory(抽象工厂类):是一个抽象类或者接口,内部定义了一系列的工厂方法(当然也是抽象的)。
ConcreteFactory(具体工厂类):继承自ABstractFactory,是实现了内部所有工厂方法用来创造一系列具体实例的类。
AbstractProduct和Product就相当于上面一张图里的Product和ConcreteProduct,只是数量变多了,并没有什么区别。
同样结合具体的例子来理解事半功倍。
一个简单的实例
我们沿用上面的例子来介绍,我们注意到,每个战士都是有武器的,但是职业相同的不同种族的武器是一模一样的,这是不合适的,毕竟种族都不同了,科技和生产肯定也不一样啊,所以我们要想办法分别制作他们的武器,因为我们要造的不只是武器(weapon)还有盔甲(armor)所以用工厂方法是实现不了了(工厂方法只能生成一种类),于是我们可以用抽象工厂来实现,首先,我们要把所有的武器装备对象化,我们要先创造两个基类Weapon和Armor,因为非常简单所有不用写成抽象类,但是一般实际境况中会有一些额外的武器属性,需要设置成抽象方法在子类实现。
1 public class Weapon { 2 String name; 3 String getName(){ 4 return name; 5 } 6 } 7 8 public class Armor { 9 String name; 10 String getName(){ 11 return name; 12 } 13 }
然后我们就可以把上面四个角色的八个装备都写出来了。
1 public class HumanArcherWeapon extends Weapon { 2 public HumanArcherWeapon(){ 3 super(); 4 name="人族弓箭手老弓"; 5 } 6 } 7 8 public class HumanArcherArmor extends Armor { 9 public HumanArcherArmor() { 10 super(); 11 name="人族弓箭手轻盔"; 12 } 13 } 14 15 public class HumanEnchanterWeapon extends Weapon { 16 public HumanEnchanterWeapon(){ 17 super(); 18 name="人族魔法师智杖"; 19 } 20 } 21 22 public class HumanEnchanterArmor extends Armor { 23 public HumanEnchanterArmor() { 24 super(); 25 name="人族魔法师法袍"; 26 } 27 } 28 29 30 public class SpriteArcherWeapon extends Weapon { 31 public SpriteArcherWeapon() { 32 super(); 33 name="妖族弓箭手老弓"; 34 } 35 } 36 37 public class SpriteArcherArmor extends Armor { 38 public SpriteArcherArmor() { 39 super(); 40 name="妖族弓箭手轻盔"; 41 } 42 } 43 44 public class SpriteEnchanterWeapon extends Weapon { 45 public SpriteEnchanterWeapon(){ 46 super(); 47 name="妖族魔法师智杖"; 48 } 49 } 50 51 public class SpriteEnchanterArmor extends Armor { 52 public SpriteEnchanterArmor() { 53 super(); 54 name="妖族魔法师法袍"; 55 } 56 }
现在我们可以写我们的抽象工厂了,里面有两个抽象工厂方法,留给子类实现,这是一个接口。
1 public interface EquipmentFactory { 2 public Armor createArmor(); 3 public Weapon createWeapon(); 4 }
现在我们来实现人族装备工厂和妖族装备工厂。
首先人族装备工厂。
1 public class HumanEquipmentFactory implements AbstractFactory { 2 public Armor createArmor(String armor) { 3 if(armor.equalsIgnoreCase("HumanArcherArmor")) 4 return new HumanArcherArmor(); 5 else if(armor.equalsIgnoreCase("HumanEnchanterArmor")) 6 return new HumanEnchanterArmor(); 7 else { 8 System.out.println("没有这个人族盔甲"); 9 return null; 10 } 11 } 12 @Override 13 public Weapon createWeapon(String weapon) { 14 if(weapon.equalsIgnoreCase("HumanArcherWeapon")) 15 return new HumanArcherWeapon(); 16 else if(weapon.equalsIgnoreCase("HumanEnchanterWeapon")) 17 return new HumanEnchanterWeapon(); 18 else { 19 System.out.println("没有这个人族武器"); 20 return null; 21 } 22 } 23 }
然后是妖族工厂。
1 public class SpriteEquipmentFactory implements EquipmentFactory { 2 @Override 3 public Armor createArmor(String armor) { 4 if(armor.equalsIgnoreCase("SpriteArcherArmor")) 5 return new SpriteArcherArmor(); 6 else if(armor.equalsIgnoreCase("SpriteEnchanterArmor")) 7 return new SpriteEnchanterArmor(); 8 else { 9 System.out.println("没有这个妖族盔甲"); 10 return null; 11 } 12 } 13 14 @Override 15 public Weapon createWeapon(String weapon) { 16 if(weapon.equalsIgnoreCase("SpriteArcherWeapon")) 17 return new SpriteArcherWeapon(); 18 else if(weapon.equalsIgnoreCase("SpriteEnchanterWeapon")) 19 return new SpriteEnchanterWeapon(); 20 else { 21 System.out.println("没有这个妖族武器"); 22 return null; 23 } 24 } 25 }
最后我们要修改一部分工厂方法里面的例子里写过的类,把我们分配武器的工厂类糅合进去,注意这里的工厂类会作为实例成员加入到兵工厂里面去,也就是说抽象工厂实现创造具体实例是通过组合的方法,而工厂方法是通过继承来实现的,这是两者的不同之处。
首先我们要修改的就是我们的英雄的基类,把里面的武器从字符串换成对应的类,我们把要修改的地方注释掉,然后在后面写上新的。
1 public abstract class Hero { 2 protected String name; 3 // protected String armor; 4 // protected String weapon; 5 // void prepare(){ 6 // System.out.println("身份:"+name); 7 // System.out.println("穿上盔甲:"+armor); 8 // System.out.println("拿上武器:"+weapon); 9 // } 10 protected Armor armor; 11 protected Weapon weapon; 12 abstract void prepare();//因为现在不同的种族要去不同的装备工厂那装备所以准备工作需要每个具体英雄自己实现 13 14 void practice(){ 15 System.out.println("接受训练"); 16 } 17 public String getName(){ 18 return name; 19 }
然后我们修改对应的四个具体类。
1 public class HumanArcherHero extends Hero { 2 HumanEquipmentFactory equipment;//增加一个装备工厂在这里为英雄分发装备 3 public HumanArcherHero(HumanEquipmentFactory equipment){ 4 super(); 5 name="人族弓箭手"; 6 //weapon="老弓"; 7 //armor="轻盔"; 8 this.equipment=equipment; 9 } 10 @Override 11 void prepare() { 12 weapon=equipment.createWeapon("HumanArcherWeapon"); 13 armor=equipment.createArmor("HumanArcherArmor"); 14 System.out.println("身份:"+name); 15 System.out.println("穿上盔甲:"+armor.getName()); 16 System.out.println("拿上武器:"+weapon.getName()); 17 } 18 }
1 public class HumanEnchanterHero extends Hero { 2 HumanEquipmentFactory equipment;//增加一个装备工厂在这里为英雄分发装备 3 public HumanEnchanterHero(HumanEquipmentFactory equipment){ 4 name="人族魔法师"; 5 //weapon="智杖"; 6 //armor="法袍"; 7 this.equipment=equipment; 8 } 9 void prepare() { 10 weapon=equipment.createWeapon("HumanEnchanterWeapon"); 11 armor=equipment.createArmor("HumanEnchanterArmor"); 12 System.out.println("身份:"+name); 13 System.out.println("穿上盔甲:"+armor.getName()); 14 System.out.println("拿上武器:"+weapon.getName()); 15 } 16 }
1 public class SpriteEnchanterHero extends Hero { 2 SpriteEquipmentFactory equipment;//增加一个装备工厂在这里为英雄分发装备 3 public SpriteEnchanterHero(SpriteEquipmentFactory equipment){ 4 name="妖族魔法师"; 5 //weapon="智杖"; 6 //armor="法袍"; 7 this.equipment=equipment; 8 } 9 @Override 10 void prepare() { 11 weapon=equipment.createWeapon("SpriteEnchanterWeapon"); 12 armor=equipment.createArmor("SpriteEnchanterArmor"); 13 System.out.println("身份:"+name); 14 System.out.println("穿上盔甲:"+armor.getName()); 15 System.out.println("拿上武器:"+weapon.getName()); 16 } 17 }
1 public class SpriteArcherHero extends Hero { 2 SpriteEquipmentFactory equipment;//增加一个装备工厂在这里为英雄分发装备 3 public SpriteArcherHero(SpriteEquipmentFactory equipment){ 4 name="妖族弓箭手"; 5 //weapon="老弓"; 6 //armor="轻盔"; 7 this.equipment=equipment; 8 } 9 @Override 10 void prepare() { 11 weapon=equipment.createWeapon("SpriteArcherWeapon"); 12 armor=equipment.createArmor("SpriteArcherArmor"); 13 System.out.println("身份:"+name); 14 System.out.println("穿上盔甲:"+armor.getName()); 15 System.out.println("拿上武器:"+weapon.getName()); 16 } 17 }
最后让我们更新我们的两个兵工厂。
1 public class HumanArmoury extends Armoury { 2 @Override 3 protected Hero createHero(String type) { 4 if(type.equalsIgnoreCase("humanEnchanterHero")){ 5 return new HumanEnchanterHero(new HumanEquipmentFactory());//return new HumanEnchanterHero(); 6 }else if(type.equalsIgnoreCase("humanArcherHero")){ 7 return new HumanArcherHero(new HumanEquipmentFactory());//return new HumanArcherHero(); 8 }else{ 9 System.out.println("目前没有这个兵种\n"); 10 return null; 11 } 12 } 13 }
1 public class SpriteArmoury extends Armoury { 2 @Override//增加一个装备工厂在这里为英雄分发装备 3 protected Hero createHero(String type) { 4 if(type.equalsIgnoreCase("SpriteArcherHero")) 5 return new SpriteArcherHero(new SpriteEquipmentFactory());//return new SpriteArcherHero(); 6 else if(type.equalsIgnoreCase("SpriteEnchanterHero")) 7 return new SpriteEnchanterHero(new SpriteEquipmentFactory());//return new SpriteEnchanterHero(); 8 else { 9 System.out.println("目前没有这个兵种\n"); 10 return null; 11 } 12 } 13 }
好了,我们的所有代码都改完了,最后我们的测试代码是不用变化的,因为我们的测试代码是并不依赖与具体类,所以保持原样就可以。下面我们直接给出输出:
看我们的的英雄拿到了专属的武器,到这来,工厂模式的两个形式就都结束了,不知道说的清不清楚呢,有任何疑问和错误请直接留言给我@呆代待殆大家共同进步♪(^∇^*)
工厂方法和抽象工厂的辨析:
1,他们都用来创建对象,但是工厂方法通过继承来实现(具体事例继承并重写工厂方法),而抽象工厂通过组合来实现(具体实例将抽象工厂的某一个具体实例作为自己的一个实例成员)
2,工厂方法只创建一种类型的实例,而抽象工厂一般能创建多种相关类型的实例,并且每一个类型用一个工厂方法来实现,所以抽象工厂内部一般是多个工厂方法。
参考资料:《Head First 设计模式》