设计模式04—工厂模式

上一篇:《设计模式03—装饰者模式》

4.工厂模式

针对接口编程,可以隔离掉以后系统可能发生的一大堆改变。
为什么呢?如果代码是针对接口而写,那么通过多态,它可以与任何新类实现该接口。但是,当代码使用大量的具体类时,等于是自找麻烦,因为一旦加入新的具体类就必须改变代码。
也就是说,你的代码并非关闭”。想用新的具体类型来扩展代码,必须重新打开它。
所以当遇到这样的问题时,该怎么办?,就应该回到OO设计原则去寻找线索。我们的第一个原则用来处理改变,并帮助我们“找出会变化的方面,把它们从不变的部分分离出来”。
针对这个问题我们开始识别变化的方面
假设我们有一个披萨店,我们对于订购披萨的代码可能如下所示:

public Pizza OrderPizza() {
    Pizza pizza=new Pizza();

    pizza.prepare();
    pizza.bake();
    pizza.cut();
    pizza.box();
    return pizza;
}

为了让系统有弹性,我们很希望这是一个抽象类或接口。但是如果这样,这些类或接口就无法直接实例化。
但是我们需要更多的披萨类型,所以必须增加一些代码来决定适合的披萨类型,在进行制造:

public Pizza OrderPizza(String type) {
    Pizza pizza=new Pizza();

    if(type.equals("cheese")){
        pizza=new CheesePizza();
    }else if(type.equals("greek")){
        pizza=new GreekPizza();
    }

    pizza.prepare();
    pizza.bake();
    pizza.cut();
    pizza.box();
    return pizza;
}

这种代码在实际使用中,一旦新增或者去掉一种披萨,那么我们的代码就要重写,比如由于芝士披萨卖的不好,我们需要从菜单中去除,所以代码就会以下所示:

public Pizza OrderPizza(String type) {
    Pizza pizza=new Pizza();

    if(type.equals("greek")){
        pizza=new GreekPizza();
    }

    pizza.prepare();
    pizza.bake();
    pizza.cut();
    pizza.box();
    return pizza;
}

但是始终保持不变的还是披萨的prepare,bake,cut,box
所以我们将变化的提取出来,然后把这个变化的部分搬到另外一个对象中,这个新对象只负责如何创建披萨,我们称这个新对象为“工厂”,于是我们的工厂模式就应运而生了,为了后面方便叙述,我们把这个新对象命名为SimplePizzaFactory。
工厂(factory)处理创建对象的细节。一旦有了SimplePizzaFactory ,orderPizza()就变成此对象的客户。当需要比萨时,就叫比萨工厂做一个。那些orderPizza()方法需要知道希腊比萨或者蛤螂比萨的日子一去不复返了。现在orderPizza()方法只关心从工厂得到了一个比萨,而这个比萨实现了Pizza接口,所以它可以调用prepare(),bake(),cut(),box()来分别进行准备、烘烤、切片、装盒。
下面我们先建立一个简单的披萨工厂
先从工厂本身开始。我们要定义一个类,为所有比萨封装创建对象的代码。代码像这样……

/**
 * 它只做一件事,帮他的客户创建披萨
 */
public class SimplePizzaFactory {

    public Pizza createPizza(String type) {
        Pizza pizza = null;
        if (type.equals("cheese")) {
            pizza = new CheesePizza();
        } else if (type.equals("pepperoniPizza")) {
            pizza = new PepperoniPizza();
        } else if (type.equals("clam")) {
            pizza = new ClamPizza();
        } else if (type.equals("veggie")) {
            pizza = new VeggiePizza();
        }
        return pizza;

    }
}

接下来我们重做PizzaStroe类

public class PizzaStore {
    SimplePizzaFactory factory;

    public PizzaStore(SimplePizzaFactory factory) {
        //构造器,需要一个工厂作为参数
        this.factory = factory;
    }

    public Pizza orderPizza(String type) {
        Pizza pizza;
        pizza = factory.createPizza(type);
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }
}

定义简单工厂

简单工厂其实不是一个设计模式,反而比较像是种编程习惯
不要因为简单工厂不是一个“真正的”模式,就忽略了它的用法。让我们来看看新的比萨店类图:
在这里插入图片描述

注意:在设计模式中,所调的“实现一个接口“不一定”表示“写一个类,并利用implements关键词来实现某个接口,实现接口泛指实现某个超类型(可以是类也可以是接口)的某个方法

加盟披萨店

每家加盟店都可能想要提供不同风味的比萨(比方说纽约、芝加哥、加州),这受到了开店地点及该地区比萨美食家口味的影响。
在这里插入图片描述

我们如何去做呢?
首先先看看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;
    }

    abstract Pizza createPizza(String type);//把工厂对象移到这个方法中
}
 

现在已经有一个PizzaStore作为超类;
让每个域类型(NYPizzaStore,ChicagoPizzaStore、CaliforniaPizzaStore)都继承这个PizzaStore,每个子类各自决定如何制造比萨。
在这里插入图片描述

现在,更进一步地,orderPizza()方法对Pizza对象做了许多事情(例如:准备、烘烤、切片、装盒),但由于Pizza对象是抽象的,orderPizza()并不知道哪些实际的具体类参与进来了。换句话说,这就是解耦(decouple)!
在这里插入图片描述

当orderPizza()调用createPizza()时,某个比萨店子类将负责创建比萨。
做哪一种比萨呢?
当然是由具体的比萨店来决定
那么,子类是实时做出这样的决定吗?
不是,但从orderPizza()的角度来看,如果选择在NYStylePizzaStore订购比萨,就是由这个子类(NYStylePizzaStore)决定。
严格来说,并非由这个子类实际做“决定”,而是由“顾客”决定到哪一家风味的比萨店才决定了比萨的风味。
于是我们一次看看NYPizzaStore(纽约风味的披萨)长什么样子吧

/**
 * 纽约风格的披萨商店
 */
public class NYPizzaStore extends PizzaStore {

    @Override
    Pizza createPizza(String type) {
        if (type.equals("cheese")) {
            return new NYStyleCheesePizza();
        } else if (type.equals("veggie")) {
            return new NYStyleVeggiePizza();
        } else if (type.equals("clam")) {
            return new NYStyleClamPizza();
        } else if (type.equals("pepperoni")) {
            return new NYStylePepperoniPizza();
        }
        return null;

    }
}
  • Pizza
      /**
      * 披萨的基类
      */
      abstract public class Pizza {
          String name;//披萨的名称
          String dough;//披萨的所用的面料
          String sauce;//披萨所用的酱料
          List<String> toppings = new ArrayList<String>();
    
          public String getName() {
              return name;
          }
    
          //准备披萨
          public void prepare() {
              System.out.println("Preparing " + name);
          }
    
          //烘烤披萨
          public void bake() {
              System.out.println("Baking " + name);
          }
    
          //裁剪披萨
          public void cut() {
              System.out.println("Cutting " + name);
          }
    
          //打包披萨
          public void box() {
              System.out.println("Boxing " + name);
          }
    
          public String toString() {
              // code to display pizza name and ingredients
              StringBuffer display = new StringBuffer();
              display.append("---- " + name + " ----\n");
              display.append(dough + "\n");
              display.append(sauce + "\n");
              for (String topping : toppings) {
                  display.append(topping + "\n");
              }
              return display.toString();
          }
      }
    
    
  • NYStyleCheesePizza
      /**
      * 纽约风味的芝士披萨
      */
      public class NYStyleCheesePizza extends Pizza {
    
          public NYStyleCheesePizza() {
              name = "纽约风味的芝士披萨";
              dough = "薄皮面团";
              sauce = "蕃茄酱";
    
              toppings.add("雷吉亚诺奶酪");
          }
      }
    
    
    
  • NYStyleVeggiePizza
      /**
      * 纽约风味的蔬菜披萨
      */
      public class NYStyleVeggiePizza extends Pizza {
          public NYStyleVeggiePizza() {
              name = "纽约风味的蔬菜披萨";
              dough = "薄皮面团";
              sauce = "番茄酱";
    
              toppings.add("奶酪");
              toppings.add("大蒜");
              toppings.add("大葱");
              toppings.add("蘑菇");
              toppings.add("红辣椒");
          }
      }
    
  • NYStyleClamPizza
      /**
      * 纽约风味的蛤蜊披萨
      */
      public class NYStyleClamPizza extends Pizza {
          public NYStyleClamPizza() {
              name = "纽约风味的蛤蜊披萨";
              dough = "薄皮面团";
              sauce = "番茄酱";
    
              toppings.add("雷吉亚诺奶酪");
              toppings.add("来自长岛海峡的新鲜蛤蜊");
          }
      }
    
  • NYStylePepperoniPizza
      /**
      * 纽约风味的意大利香肠披萨
      */
      public class NYStylePepperoniPizza extends Pizza {
          public NYStylePepperoniPizza() {
              name = "纽约风味的意大利香肠披萨";
              dough = "薄皮面团";
              sauce = "咖喱酱";
    
              toppings.add("奶酪");
              toppings.add("香肠");
              toppings.add("大蒜");
              toppings.add("洋葱");
              toppings.add("蘑菇");
              toppings.add("红辣椒");
          }
      }
    
    

接下来我们继续实现芝加哥的披萨店

/**
 * 芝加哥的披萨店
 */
public class ChicagoPizzaStore extends PizzaStore {
    @Override
    Pizza createPizza(String type) {
        if (type.equals("cheese")) {
            return new ChicaoStyleCheesePizza();
        } else if (type.equals("veggie")) {
            return new ChicaoStyleVeggiePizza();
        } else if (type.equals("clam")) {
            return new ChicaoStyleClamPizza();
        } else if (type.equals("pepperoni")) {
            return new ChicaoStylePepperoniPizza();
        }
        return null;
    }
}

自己可以模仿着纽约风味的披萨,写出ChicaoStyleCheesePizza,ChicaoStyleVeggiePizza,ChicaoStylePepperoniPizza,就像下面的ChicaoStyleCheesePizza一样

/**
 * 芝加哥风味的芝士披萨
 */
public class ChicaoStyleCheesePizza extends Pizza {

    public ChicaoStyleCheesePizza() {
        name = "芝加哥风味的芝士披萨";
        dough = "厚皮面团";
        sauce = "蕃茄酱";
        toppings.add("雷吉亚诺奶酪");
    }
}

工厂方法用来处理对象的创建,并将这样的行为封装在子类中。这样,客户程序中关于超类的代码就和子类对象创建代码解耦了。
在这里插入图片描述

接下来我们回顾一下订单是如何生成披萨的,假如我们需要订购一款纽约风味的芝士披萨

  • 1.首先我们需要一个纽约的披萨店

      PizzaStore nyPizzaStore=new NYPizzaStore();
    
  • 2.现在有一个店了,那么我们就可以下订单了

      nyPizzaStore.orderPizza("cheese");
    

    调用nyPizzaStoze实例的ordePiza方法(达个方法被定义在PizzeStore中)。

  • 3.ordePiza方法调用createPizza方法;
    在这里插入图片描述

  • 4.最后,披萨必须经过烘焙,裁剪,打包等才算完成

      pizza.prepare();
      pizza.bake();
      pizza.cut();
      pizza.box();
    

接下来我们正式订购一个披萨

public class Store {
    public static void main(String[] args) {
        //首先建立商店
        PizzaStore nyPizzaStore = new NYPizzaStore();
        PizzaStore chicaoStore = new ChicagoPizzaStore();

        Pizza pizza = nyPizzaStore.orderPizza("cheese");
        System.out.println("---------纽约风味的芝士披萨制作完成:"+pizza.toString()+"---------------------");
        pizza = chicaoStore.orderPizza("cheese");
        System.out.println("---------芝加哥风味的芝士披萨制作完成:"+pizza.toString()+"---------------------");
    }
}

运行结果如下:
在这里插入图片描述

认识工厂方法模式

所有工厂模式都用来封装对象的创建。
工厂方法模式(Factory Method Pattern)通过让子类决定该创建的对象是什么,来达到将对象创建的过程封装的目的。让我们来看看这些类图,以了解有哪些组成元素:

  • 1.创建者类
    在这里插入图片描述

  • 2.产品类
    在这里插入图片描述

平行的类层级

我们已经看到,将一个orderPizza()方法和一个工厂方法联合起来,就可以成为一个框架。除此之外,工厂方法将生产知识封装进各个创建者,这样的做法,也可以被视为是一个框架。
在这里插入图片描述

定义工厂方法模式

在这里插入图片描述

工厂方法模式能够封装具体类型的实例化。如下面的类图所示,抽象的Creator提供了一个创建对象的方法的接口,也称为“工厂方法”。在抽象的Creator中,任何其他实现的方法,都可能使用到这个工厂方法所制造出来的产品,但只有子类真正实现这个工厂方法并创建产品
在这里插入图片描述

工厂方法让子类决定要实例化的类是哪一个。
希望不要理解错误,所谓的“决定”,并不是指模式允许子类本身在运行时做决定,而是指在编写创建者类时,不需要知道实际创建的产品是哪一个。选择了使用哪个子类,自然就决定了实际创建的产品是什么。

  • 当只有一个ConcreteCreator的时候,工厂方法模式有什么优点?

    尽管只有一个具体创建者,工厂方法模式依然很有用,因为它帮助我们将产品的“实现”从“使用”中解耦。如果增加产品或者改变产品的实现,Creator并不会受到影响(因为Creator与任何ConcreteProduct之间都不是紧耦合)。

对象依赖,依赖倒置原则

当我们直接实例化一个对象时,就是在依赖它的具体类
何为依赖倒置原则:
要依赖抽象,不要依赖具体类
首先,这个原则听起来很像是“针对接口编程,不针对实现编程”,然而这里更强调“抽象”。
这个原则说明了:不能让高层组件依赖低层组件,而且,不管高层或低层组件,“两者”都应该依赖于抽象。
所调“高层”组件.是由其他低层组件定义其行为的类。例如,PizzaStore是个高层组件.团为它的行为是由披萨定义的:PixzaStore创建所有不同的比萨对象,准备、烘烤、切片、装盒;而披萨本属于低层组件。
所以我们应用工厂模式之后,我们的类图应该更像下图所示:
在这里插入图片描述

在应用工厂方法之后,你将注意到,高层组件(也就是PizzaStore)和低层组件(也就是这些比萨)都依赖了Pizza抽象。想要遵循依赖倒置原则,工厂方法并非是唯的技巧,但却是最有威力的技巧之一。
下面的指导方针,能帮你避免在OO设计中违反依赖倒置原则:

  • 1.变量中不可以持有具体类的引用。

    如果使用New,就会持有具体类的引用。你可以改用工厂来避开这样的做法

  • 2.不要让类派生自具体类。

    如果派生自具体类,那么我们就会依赖具体类,请派生自一个抽象(接口或抽象类)

  • 3.不要覆盖基类中已经实现的方法

    如果覆盖基类已经=实现的方法,那么我们的基类就不是一个真正适合被继承的抽象。基类中已经实现的方法,应该由所有的子类共享。

接下来我们继续回到披萨店,为了保证披萨口感,我们需要建立一个原料工厂,来确保原料的一致,下面是纽约和芝加哥的两组不同的原料
在这里插入图片描述

建造原料工厂

现在,我们要建造一个工厂来生产原料;这个工厂将负责创建原料家族中的每一种原料。也就是说,工广将需要生产面团、酱料、芝士等。待会儿,你就会知道如何处理各个区域的差异了。
开始先为工厂定义一个接口,这个接口负责创建所有的原料:

/**
 * 原料工厂
 */
public interface PizzaIngredientFactory {
    public Dough createDough();

    public Sauce createSauce();

    public Cheese createCheese();

    public Veggies[] createVeggies();

    public Pepperoni createPepperoni();

    public Clams createClam();
}

这里有许多的新类,每一个原料都是一个类
而我们要做的事情就是:

  • 1.为每个区域建造一个工厂。你需要创建一个继承自PizzaIngredientFactory的子类来实现每一个创建方法。
  • 2.实现一组原料类供工厂使用,例如ReggianoCheese、RedPeppers、ThickCrust-Dough。这些类可以在合适的区域间共享。
  • 3.然后你仍然需要将这一切组织起来,将新的原料工厂整合进旧的PizzaStore代码中
    对应的Dough,Sauce,Cheese,Veggies,Pepperoni,Clams如下
  • 1.Dough
      /**
      * 面团
      */
      public interface Dough {
          public String toString();
      }
    
  • 2.Sauce
      /**
      * 酱
      */
      public interface Sauce {
          String toString();
      }
    

其他的几个抽象接口也如上面两个一样,自己可以尝试编写
接下来我们实现纽约原料工厂,该工厂精于大蒜番茄酱料,Reggiano干酪,新鲜蛤蜊等

public class NYPizzaIngredientFactory implements PizzaIngredientFactory {
    @Override
    public Dough createDough() {
        return new ThinCrustDough();
    }

    @Override
    public Sauce createSauce() {
        return new MarinaraSauce();
    }

    @Override
    public Cheese createCheese() {
        return new ReggianoCheese();
    }

    @Override
    public Veggies[] createVeggies() {
        Veggies veggies[] = {
                new Garlic(), new Onion(), new Mushroom(), new RedPepper()
        };
        return veggies;
    }

    @Override
    public Pepperoni createPepperoni() {
        return new SlicedPepperoni();
    }

    @Override
    public Clams createClam() {
        return new FeshClams();
    }
}

其中ThinCrustDough,MarinaraSauce等如下:

  • ThinCrustDough
      public class ThinCrustDough implements Dough{
          @Override
          public String toString() {
              return "薄皮面团";
          }
      }
    
  • MarinaraSauce
      public class MarinaraSauce implements Sauce{
          @Override
          public String toString() {
              return "蕃茄酱";
          }
      }
    

剩下的自己去编写即可,本文就不过多的赘述
接下来我们去实现芝加哥的食料工厂

/**
 * 芝加哥风味的食料工厂
 */
public class ChicagoPizzaIngredientFactory implements PizzaIngredientFactory {
    @Override
    public Dough createDough() {
        return new ThickCrustDough();
    }

    @Override
    public Sauce createSauce() {
        return new PlumTomatoSauce();
    }

    @Override
    public Cheese createCheese() {
        return new MozzarellaCheese();
    }

    @Override
    public Veggies[] createVeggies() {
        Veggies veggies[] = {new BlackOlives(),
                new Spinach(),
                new Eggplant()};
        return veggies;
    }

    @Override
    public Pepperoni createPepperoni() {
        return new SlicedPepperoni();
    }

    @Override
    public Clams createClam() {
        return new FrozenClams();
    }
}

对应的ThickCrustDough,PlumTomatoSauce如下:

  • ThickCrustDough
      public class ThickCrustDough implements Dough {
          @Override
          public String toString() {
              return "厚皮面团";
          }
      }
    
    
  • 2.PlumTomatoSauce
      public class PlumTomatoSauce implements Sauce{
          @Override
          public String toString() {
              return "番茄酱配李子番茄";
          }
      }
    

其他的自己可以仿照着自己去编写
工厂均准备就绪,接下来我们需要重做披萨,让它只使用工厂生产出的原料

/**
 * 改版之后的披萨,只能使用我们工厂的原料
 */
public abstract class Pizza02 {
    String name;
    Dough dough;
    Sauce sauce;
    Veggies veggies[];
    Cheese cheese;
    Pepperoni pepperoni;
    Clams clam;

    abstract void prepare();

    void bake() {
        System.out.println("Bake for 25 minutes at 350");
    }

    void cut() {
        System.out.println("Cutting the pizza into diagonal slices");
    }

    void box() {
        System.out.println("Place pizza in official PizzaStore box");
    }

    void setName(String name) {
        this.name = name;
    }

    String getName() {
        return name;
    }

    public String toString() {
        StringBuffer result = new StringBuffer();
        result.append("---- " + name + " ----\n");
        if (dough != null) {
            result.append(dough);
            result.append("\n");
        }
        if (sauce != null) {
            result.append(sauce);
            result.append("\n");
        }
        if (cheese != null) {
            result.append(cheese);
            result.append("\n");
        }
        if (veggies != null) {
            for (int i = 0; i < veggies.length; i++) {
                result.append(veggies[i]);
                if (i < veggies.length - 1) {
                    result.append(", ");
                }
            }
            result.append("\n");
        }
        if (clam != null) {
            result.append(clam);
            result.append("\n");
        }
        if (pepperoni != null) {
            result.append(pepperoni);
            result.append("\n");
        }
        return result.toString();
    }
}

继续重做披萨
现在已经有了一个抽象比萨,可以开始创建纽约和芝加哥风味的比萨了。从今以后,加盟店必需直接从工厂取得原料,那些偷工减料的日子宣告结束了!
我们曾经写过工厂方法的代码,有NYCheesePizza和ChicagoCheesePizza类。比较一下这两个类,唯一的差别在于使用区域性的原料,至于比萨的做法都一样(面团+酱料+芝士),其他的比萨(蔬菜、蛤蝴等)也是如此。它们都依循着相同的准备步骤,只是使用不同的原料。
所以,其实我们不需要设计两个不同的类来处理不同风味的比萨,让原料工厂处理这种区域差异就可以了。下面是CheesePizza :

/**
 * 只能使用自己工厂的原料的芝士披萨
 */
public class CheesePizza02 extends Pizza02 {
    PizzaIngredientFactory pizzaIngredientFactory;

    public CheesePizza02(PizzaIngredientFactory pizzaIngredientFactory) {
        this.pizzaIngredientFactory = pizzaIngredientFactory;
    }

    @Override
    void prepare() {
        //preare方法会一步一步的创建芝士披萨,每当需要什么材料只需向食料工厂去要即可
        System.out.println("正在准备:" + name);
        dough = pizzaIngredientFactory.createDough();
        sauce = pizzaIngredientFactory.createSauce();
        cheese = pizzaIngredientFactory.createCheese();
    }
}

Pizza的代码利用相关的工厂生产原料。所生产的原料依赖所使用的工厂,Pizza类根本不关心这些原料,它只知道如何制作比萨。现在,Pizza和区域原料之间被解耦,无论原料工厂是在洛基山脉还是在西北沿岸地区,Pizza类都可以轻易地复用,完全没有问题。
在这里插入图片描述

接下来我们继续实现蛤蜊披萨

/**
 * 只能使用工厂原料制作的蛤蜊披萨
 */
public class ClamPizza02 extends Pizza02 {
    PizzaIngredientFactory pizzaIngredientFactory;

    public ClamPizza02(PizzaIngredientFactory pizzaIngredientFactory) {
        this.pizzaIngredientFactory = pizzaIngredientFactory;
    }

    @Override
    void prepare() {
        //preare方法会一步一步的创建芝士披萨,每当需要什么材料只需向食料工厂去要即可
        System.out.println("正在准备:" + name);
        dough = pizzaIngredientFactory.createDough();
        sauce = pizzaIngredientFactory.createSauce();
        cheese = pizzaIngredientFactory.createCheese();
    }
}

做完以上工作,我们基本完工,我们继续回到披萨店

/**
 * 使用工厂原料的披萨商店
 */
public abstract class PizzaStore02 {
    public Pizza02 orderPizza(String type) {
        Pizza02 pizza;
        pizza = createPizza(type);//将创建披萨的方法从工厂中移回
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }

    abstract Pizza02 createPizza(String type);//把工厂对象移到这个方法中
}

继续构建纽约风味的披萨商店

/**
 * 使用工厂原料的纽约风味的披萨超市
 */
public class NYPizzaStore02 extends PizzaStore02 {
    @Override
    Pizza02 createPizza(String type) {
        Pizza02 pizza = null;
        PizzaIngredientFactory ingredientFactory = new NYPizzaIngredientFactory();//纽约店全用到纽约披萨原料工厂
        if (type.equals("cheese")) {
            pizza = new CheesePizza02(ingredientFactory);
            pizza.setName("纽约风味的芝士披萨");
        } else if (type.equals("veggie")) {
            pizza = new VeggiePizza02(ingredientFactory);
            pizza.setName("纽约风味的蔬菜披萨");
        } else if (type.equals("clam")) {
            pizza = new ClamPizza02(ingredientFactory);
            pizza.setName("纽约风味的蛤蜊披萨");
        } else if (type.equals("peoperoni")) {
            pizza = new PepperoniPizza02(ingredientFactory);
            pizza.setName("意大利辣味香肠");
        }
        return pizza;
    }
}

在这里插入图片描述

定义抽象工厂模式

在这里插入图片描述

下一篇:《设计模式05—单件模式》

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ZNineSun

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值