新增一个OO设计原则:
要依赖抽象,不要依赖具体类
1 简单工厂
- 针对接口编程,可以隔离掉以后系统可能发生的一大堆改变。为什么呢?如果代码是针对接口而写,那么通过多态,它可以与任何新类实现该接口。但是,当代码使用大量的具体类时,等于是自找麻烦,因为一旦加入新的具体类,就必须改变代码。也就是说,你的代码并非“对修改关闭”。想用新的具体类型来扩展代码,必须重新打开它。所以,当遇到这样的问题时,就应该回到OO设计原则去寻找线索。我们的第一个设计原则用来处理改变,并帮助我们“找出会变化的方面,把它们从不改变的部分分离出来“。
如下面代码示例暴露出的问题
/**
* @author hgl
* @data 2018年5月6日
* @description pizza抽象类
*/
public abstract class Pizza {
public String name;
//所有的比萨都需要做一些准备,如擀面皮,酱汁,加上配料(如芝士等)
/**
* 面团
*/
public String dough;
/**
* 酱汁
*/
public String sauce;
/**
* 配料
*/
public List toppings = new ArrayList();
public void prepare(){
System.out.println("Preparing "+name);
System.out.println("Tossing dough...");
System.out.println("Adding sauce...");
System.out.println("Adding toppings:");
for (int i = 0; i < toppings.size(); i++) {
System.out.println(" "+toppings.get(i));
}
}
//所有的比萨都会有这些动作
/**
* void
* description:烤的动作
*/
public void bake(){
System.out.println("Bake for 25 minutes at 350");
}
/**
* void
* description:切的动作
*/
public void cut(){
System.out.println("Cutting the pizza into diagonal slices");
}
/**
* void
* description:包装的动作
*/
public void box(){
System.out.println("Place piaaz in official PizzaStore box");
}
/**
* String
* @return
* description:返回这个pizza的名字
*/
public String getName(){
return name;
}
}
/**
* @author hgl
* @data 2018年5月6日
* @description 奶酪比萨
*/
public class Cheese extends Pizza {
public Cheese(){
name = "Cheese Pizza";
dough = "Thin Crust Dough";
toppings.add("cheese");
}
}
/**
* @author hgl
* @data 2018年5月6日
* @description 蛤蜊比萨
*/
public class Clam extends Pizza {
public Clam(){
name = "Clam Pizza";
}
}
/**
* @author hgl
* @data 2018年5月6日
* @description 希腊比萨
*/
public class Greek extends Pizza{
public Greek(){
name = "Greek Pizza";
dough = "Thin Crust Dough";
sauce = "Greek sauce";
toppings.add("unique toppings");
}
}
/**
* @author hgl
* @data 2018年5月6日
* @description 比萨店
*/
public class PizzaStore {
public Pizza orderPizza(String type){
Pizza pizza;
/*
if判断中的部分是变化的部分。随着时间过去,比萨菜单
会改变,比如加入新的比萨。这里就必须一改再改。
*/
if(type.equals("cheese")){
pizza = new Cheese();
}else if(type.equals("greek")){
pizza = new Greek();
}else if(type.equals("clam")){
pizza = new Clam();
}else{
throw new RuntimeException("Error:invalid type");
}
/*
下面的是不会改变的,所有的比萨必须要经过这几个程序。
*/
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
现在最好将创建对象移到orderPizza()之外,但怎么做呢?
可以把创建比萨的代码移到另外一个对象中,由这个新对象专职创建比萨。
我们称这个新对象为“工厂”
工厂处理创建对象的细节,一旦有了工厂,orderPizza就变成此对象的客户。当需要比萨时,就叫比萨工厂做一个。那些orderPizza()方法需要知道希腊比萨或者蛤蜊比萨的日子一去不复返了。现在orderPizza()方法只关心从工厂得到了一个比萨,而这个比萨实现了Pizza接口,所以它可以调用prepare(),bake(),cut(),box()来分别进行准备,烘烤,切片,装盒。
- 建立一个简单比萨工厂
/**
* @author hgl
* @data 2018年5月6日
* @description 简单比萨工厂
*/
public class SimplePizzaFactory {
public Pizza createPizza(String type){
Pizza pizza = null;
if(type.equals("cheese")){
pizza = new Cheese();
}else if(type.equals("greek")){
pizza = new Greek();
}else if(type.equals("clam")){
pizza = new Clam();
}else{
throw new RuntimeException("Error:invalid type");
}
return pizza;
}
}
这么做有什么好处?似乎把一个问题搬到另外一个对象罢了
答:别忘了,SimplePizzaFactory可以有许多的客户。虽然目前只看到orderPizza()方法是它的客户,然而,可能还有PizzaShopMenu(比萨菜单)类会利用这个工厂来取得比萨的价钱和描述。等等。所以,把创建比萨的代码包装进一个类,当以后实现改变时,只需要修改这个类即可。
新的PizzaStore的代码:
/**
* @author hgl
* @data 2018年5月6日
* @description 比萨店
*/
public class PizzaStore {
SimplePizzaFactory factory;
public PizzaStore(SimplePizzaFactory factory) {
super();
this.factory = factory;
}
public Pizza orderPizza(String type){
Pizza pizza;
pizza = factory.createPizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
简单工厂其实不是一个设计模式,反而比较像是一种编程习惯,但是经常会被使用。
2 工厂方法
现在出现了很多加盟店,而我们希望确保加盟店营运的质量,但是区域的差异呢?每家加盟店都可能想要提供不同风味的比萨(比方说纽约,芝加哥,加州)。质量怎么控制?比如加盟店采用它们自创的流程:烘烤的做法有些差异,不要切片,使用其他厂商的盒子。
我们希望建立一个框架,把加盟店和创建比萨捆绑在一起的同时又保持一定的弹性。
有个做法可让比萨制作活动局限于PizzaStore类,而同时又能让这些加盟店依然可以自由地制作该区域的风味。
所要做的事情,就是把createPizza()方法放回到PizzaStore中,不过要把它设置成“抽象方法”,然后为每个区域风味创建一个PizzaStore的子类。
改变后的代码:
public abstract class PizzaStore {
public Pizza orderPizza(String type){
Pizza pizza;
pizza = createPizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
/*
现在,实例化比萨的责任被移到一个方法中,此方法就如同是
一个“工厂”.
工厂方法是抽象的,所以依赖子类来处理对象的创建
工厂方法必须返回一个产品。超类中定义的方法,通常使用到工厂方法的返回值
工厂方法将客户(也就是超类中的代码,例如orderPizza())和实际创建
具体产品的代码分割开来。
*/
public abstract Pizza createPizza(String type);
}
/**
* @author hgl
* @data 2018年5月6日
* @description 纽约店
*/
public class NYPizzaStore extends PizzaStore{
@Override
public Pizza createPizza(String type) {
/*
* 这里我只写了一个纽约pizza,其实有很多
*/
if(type.equals("cheese")){
return new NYStyleCheesePizza();
}else return null;
}
}
/**
* @author hgl
* @data 2018年5月6日
* @description 芝加哥店
*/
public class ChicagoStore extends PizzaStore {
@Override
public Pizza createPizza(String type) {
//这里我也只写了一个,当然还有很多中披萨
if(type.equals("cheese")){
return new ChicagoStyleCheesePizza();
}
return null;
}
}
/**
* @author hgl
* @data 2018年5月6日
* @description 纽约风味的pizza
*/
public class NYStyleCheesePizza extends Pizza {
public NYStyleCheesePizza(){
/*
* 纽约比萨有自己的大蒜番茄酱和薄饼
*/
name = "NY style Sauce and Cheese Pizza";
dough = "Thin Crust Dough";
sauce = "Marinara Sauce";
/*
* 上面覆盖的是意大利reggiano高级奶酪
*/
toppings.add("Grated Reggiano Cheese");
}
}
public class ChicagoStyleCheesePizza extends Pizza {
public ChicagoStyleCheesePizza(){
/*
* 芝加哥比萨使用小番茄作为酱料,并使用厚饼
*/
name = "Chicago Style Deep DIsh Cheese Pizza";
dough = "Extra Thick Crust Dough";
sauce = "Plum Tomato Sauce";
/*
* 芝加哥风味的深盘比萨使用许多mozzarella(意大利白干酪)
*/
toppings.add("Shredded Mozzarella Cheese");
}
/*
* 这个芝加哥风味比萨覆盖类cut()方法,将比萨切成正方形
*/
public void cut(){
System.out.println("Cutting the pizza into square slices");
}
}
public class PizzaTestDrive {
public static void main(String[] args) {
PizzaStore nyStore = new NYPizzaStore();
PizzaStore chicagoStore = new ChicagoStore();
Pizza pizza = nyStore.orderPizza("cheese");
System.out.println("Ethan ordered a "+pizza.getName()+"\n");
pizza = chicagoStore.orderPizza("cheese");
System.out.println("Joel ordered a "+pizza.getName()+"\n");
}
}
所有工厂模式都用来封装对象的创建。工厂方法模式(Factory Method Pattern)通过让子类决定该创建的对象是什么,来达到将对象创建的过程封装的目的。
工厂方法模式:定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。
- 依赖倒置原则
要依赖抽象,不要依赖具体类
即:不能让高层组件依赖低层组件,而且不管高层或低层组件,“两者”都应该依赖于抽象。
所谓“高层”组件,是由其他低层组件定义其行为的类。例如,PizzaStore是个高层组件,因为它的行为是由比萨定义的:PizzaStore创建所有不同的比萨对象,准备,烘烤,切片,装盒,而比萨本身属于低层组件。
看一段代码:
/*
这个版本的PizzaStore依赖于所有的比萨对象,因为它直接创建这些比萨对象
如果这些类的实现改变了,那么可能必须修改PizzaStore
每新增一个比萨种类,就等于让PizzaStore多了一个依赖
因为对于比萨具体实现的任何改变都会影响到PizzaStore。我们说PizzaStore“依赖于”比萨的实现
*/
public class DependentPizzaStore {
public Pizza createPizza(String style,String type){
Pizza pizza = null;
if(style.equals("NY")){
if(type.equals("cheese")){
pizza = new NYStyleCheesePizza();
}
}else if (style.equals("Chicago")){
if(type.equals("cheese")){
pizza = new ChicagoStyleCheesePizza();
}
}else {
System.out.println("Erro:invalid type of pizza");
return null;
}
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
当直接实例化一个对象时,就是在依赖它的具体类。上面的这个比萨店,它由比萨店类创建所有的比萨对象,而不是委托给工厂。
现在,这个原则告诉我们,应该重写代码以便于我们依赖抽象类,而不依赖具体类。对于高层及低层模块都应该如此。
之前运用工厂方法之后,高层组件PizzaStore和低层组件(也就是这些比萨)都依赖了Pizza对象。高层组件并没有依赖低层组件,而且两者都依赖了抽象,低层组件依赖高层的抽象。
- 几个指导方针帮助你遵循此原则
*变量不可以持有具体类的引用:如果使用new,就会持有具体类的引用。你可以用工厂来避开这样的做法。
*不要让类派生自具体类:如果派生自具体类,就会依赖具体类。
*不要覆盖基类中已实现的方法:如果覆盖基类已实现的方法,那么这个基类就不是一个真正适合被继承的抽象。基类中已实现的方法,应该由所有的子类共享。
3 抽象工厂
生产原料,但是每个区域所使用的原料都不相同,比如纽约的红酱料和芝加哥的红酱料是不同的。该如何做呢?一下代码解决了这个问题
/**
* @author hgl
* @data 2018年5月6日
* @description pizza抽象类
*/
public abstract class Pizza {
public String name;
public Dough dough;
public Sauce sauce;
public Veggies veggies[];
public Cheese cheese;
public Pepperoni pepperoni;
public Clams clam;
public abstract void prepare();
public void bake(){
System.out.println("Bake for 25 minutes at 350");
}
public void cut(){
System.out.println("Cutting the pizza into diagonal slices");
}
public void box(){
System.out.println("Place pizza in official PizzaStore box");
}
public void setName(String name){
this.name=name;
}
public String getName(){
return name;
}
public String toString(){
return name;
}
}
public interface PizzaIngredientFactory {
public Dough createDough();
public Sauce createSauce();
public Cheese CreateCheese();
public Veggies[] createVeggies();
public Pepperoni createPepperoni();
public Clams createClam();
}
public interface Dough {
}
public interface Sauce {
}
public interface Cheese {
}
public interface Clams {
}
public interface Pepperoni {
}
public interface Veggies {
}
public class ThinCrustDough implements Dough {
}
public class SlicedPepproni implements Pepperoni {
}
public class ReggianoCheese implements Cheese {
}
public class FreshClams implements Clams {
}
public class MarinaraSauce implements Sauce {
}
public class Mushroom implements Veggies {
}
public class Onion implements Veggies {
}
public class Garlic implements Veggies {
}
public class NYPizzaIngredientFactory implements PizzaIngredientFactory {
@Override
public Dough createDough() {
// TODO Auto-generated method stub
return new ThinCrustDough();
}
@Override
public Sauce createSauce() {
// TODO Auto-generated method stub
return new MarinaraSauce();
}
@Override
public Cheese CreateCheese() {
// TODO Auto-generated method stub
return new ReggianoCheese();
}
@Override
public Veggies[] createVeggies() {
// TODO Auto-generated method stub
Veggies[] veggies = {new Garlic(),new Onion(),new Mushroom()};
return veggies;
}
@Override
public Pepperoni createPepperoni() {
// TODO Auto-generated method stub
return new SlicedPepproni();
}
@Override
public Clams createClam() {
// TODO Auto-generated method stub
return new FreshClams();
}
}
/**
* @author hgl
* @data 2018年5月6日
* @description 纽约店
*/
public class NYPizzaStore extends PizzaStore{
@Override
public Pizza createPizza(String type) {
Pizza pizza = null;
PizzaIngredientFactory ingredientFactory = new NYPizzaIngredientFactory();
if(type.equals("cheese")){//只写了一个例子
pizza = new CheesePizza(ingredientFactory);
pizza.setName("New York Style Cheese Pizza");
}
return pizza;
}
}
我们引入新类型的工厂,也就是所谓的抽象工厂,来创建比萨原料家族。
通过抽象工厂所提供的接口,可以创建产品的家族,利用这个接口书写代码,我们的代码将从实际工厂解藕,以便在不同上下文中实现各式各样的工厂,制造出各种不同的产品。因为代码从实际的产品中解藕了,所以我们可以替换不同的工厂来取得不同的行为。
抽象工厂模式:提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。