从现在起,我们开始学习设计模式。
实际上,在以前的学习中已经接触到了许多的设计模式。比如,IO的装饰者模式,String常量池中的享元模式,单例模式,简单工厂、静态工厂模式,GUI监测的观察者模式,Spring中的代理模式等等。在一本书上看过,设计模式不只是一套公式,为了设计模式而设计,而是来源于程序而高于程序的一套系统的方法论。可以说任意的程序都能找到设计模式的影子,可能是一个模式,也可能是很多设计模式杂糅在一起。
虽然之前接触过,也大概了解一些设计模式的含义,但是还没有经过系统的学习。学设计模式的意义在于了解大局观,能从架构上寻找合适的解决问题方法。
GOF的中介绍,设计模式大致分为3大类:创建型模式(Creational Patterns)、结构型模式(Structural Patterns)、行为型模式(Behavioral Patterns)。在此基础之上,我们再来学习J2EE的几种设计模式。
参考:
设计模式
java常用设计模式
本篇介绍 创建者模式 ,其中包含这几个大类:
- 工厂模式(Factory Pattern)
- 抽象工厂模式(Abstract Factory Pattern)
- 单例模式(Singleton Pattern)
- 建造者模式(Builder Pattern)
- 原型模式(Prototype Pattern)
这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用新的运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。
工厂模式
工厂模式是Java中最常用的设计模式之一,提供了创建对象的最佳方式。这种情况下,创建对象时不会对客户端暴露创建的逻辑,而且通过一个公共接口来指向新的对象。
在我们明确的计划需要在不同条件下创建不同的示例时,工厂模式就是我们需要的模式。比如说,我们需要一辆汽车,只需要付款,并去工厂提货就可以了,具体这车的车体、发动机、轮毂是如何生产我们不需要关心,只需要好好用就可以了。
工厂模式的优点:
- 一个调用中想创建一个对象,只知道名称即可,不需要了解其内部实现;
- 扩展性高,每增加一个产品,只需要扩展工厂类就可以;
但同时,也有这个缺点:
- 每次增加一个产品,就需要增加一个具体类和对象实现工厂,使得系统中的类的个数直线上升,同时使得类的关系错综复杂,增加了耦合性。
使用工厂模式需要注意的是,不能滥用这个模式。如果创建的对象只需要new,那么没必要设计一个复杂的工厂类,如果使用了工厂模式,那么需要引入工厂类,增加了复杂度。
工厂模式典型例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| interface Shape { void draw(); }
class Rectangle implements Shape { @Override public void draw() { System.out.println("this is rectangle"); } } class Squre implements Shape { @Override public void draw() { System.out.println("this is squre"); } } class Circle implements Shape { @Override public void draw() { System.out.println("this is circle"); } }
class ShapeFactory { public Shape getShape (String shapeType) { if (shapeType == null) return null; switch (shapeType.toLowerCase()) { case "circle": return new Circle(); case "rectangle" : return new Rectangle(); case "squre" : return new Squre(); } return null; } }
public class FactoryPatternDemo { public static void main(String[] args) { ShapeFactory sf = new ShapeFactory(); Shape shapes[] = new Shape[3]; shapes[0] = sf.getShape("CIRCLE"); shapes[1] = sf.getShape("RECTANGLE"); shapes[2] = sf.getShape("SQURE"); for (Shape shape : shapes) shape.draw(); } }
|
抽象工厂类型
围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。抽象工厂的概念和工厂很像,都是生产对象,但区别也很大:抽象工厂是工厂的上一层,是生产工厂的工厂。比如有多个工厂生产键鼠,A是罗技,B是微软。此时就需要抽象工厂在上一层来统筹规划,指派A或者B来生产对象。如果只使用普通的工厂,生产同类型但又不同的对象需要增加方法,而抽象工厂只需要换一个工厂就可以同时替换键盘和鼠标。
所以可以这样总结:工厂模式类似于流水线,产生所需要的对象,而抽象工厂就像真正的工厂,可以使用不同流水线产生对象。
下面来介绍一个简单的例子,使用了上一个例子中的几个类:
首先像上一个例子一样写一个color接口,并新建几个color方法Red, Blue, Black。创建一个抽象工厂类:
1 2 3 4
| abstract class AbstractFatory { abstract Color getColor(String color); abstract Shape getShape(String shape); }
|
再创建工厂类AbstractColorFactory、AbstractShapeFactory实现抽象工厂。
最后创建生产工厂的方法,让外部程序调用产生对象:
1 2 3 4 5 6 7 8 9 10 11
| class FactoryProducer { public AbstractFatory getFactory (String choice) { switch (choice.toLowerCase()) { case "shape" : return new AbstractShapeFactory(); case "color" : return new AbstractColorFactory(); } return null; } }
|
单例模式
单例模式是最简单的设计模式之一。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有这一个对象被创建。可以直接访问,不需要而且不可以实例化该类的对象。
单例模式应用广泛。比如在hibernate中,创建Session需要很大的资源消耗,但这是必要的一步,而且,每次只需要使用一次即可。这种情况下,单例模式是最好的方法。减少内存的开销,避免了对资源的多重占用。
实现单例模式的方式有很多,比如懒汉模式、饿汉模式等。在此不加展开,使用用途非常广泛的这种方式:
1 2 3 4 5 6 7
| class SingleObject { private static SingleObject instance = new SingleObject(); private SingleObject(){}; public static SingleObject getInstance() { return instance; } }
|
建造者模式
建造者模式使用多个简单的对象一步一步构成复杂的对象。最终构成的对象独立于其他对象。其内涵是 将一个复杂的对象的构建与它的表示分离,使得同样的构建过程可以有不同的表示 。
在软件系统中,有时面临着一个“复杂对象”的创建工作,通常各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常需要改变,但是将它们组合在一起的算法却相对稳定。这时可以采用建造者模式。用一个director类把控流程,而用许多不同的builder去建造流程中的细节并产生产品。这样,生产出来的产品是绝对不会出问题的,因为流程把控好了。可以有多个builder去负责建造生产产品,而让director去把控流程。如果有新的产品,但是流程一致,就可以再扩张出一个builder来。
建造者模式一般包括以下几个角色:
- Builder:给出一个抽象接口,规范建造者对于生产的产品的各个组成部分的建造。这个接口只是定一个规范,不涉及具体的建造,具体的建造让继承于它的子类(ConcreteBuilder)去实现。
- ConcreteBuilder:实现builder接口,针对不同的商业逻辑,具体化各对象部分的建造,最后返回一个建造好的产品。
- Director:导演,顾名思义,负责规范流程之用。在指导中不涉及产品的创建,只负责保证复杂对象各部分被创建或按某种顺序创建。
- Product:复杂对象。
下面是建造者模式的一个例子。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
| interface Item { public String name(); public Packing packing(); public float price(); } interface Packing { public String pack(); } class Wrapper implements Packing { public String pack() { return "Wrapper"; } } class Bottle implements Packing { public String pack() { return "Bottle"; } }
abstract class Burger implements Item { public Packing packing() { return new Wrapper(); } public abstract float price(); } abstract class ColdDrink implements Item { public Packing packing() { return new Bottle(); } public abstract float price(); }
class VegBurger extends Burger { public String name() { return "Veg Burger"; } public float price() { return 25.0f; } } class ChickenBurger extends Burger { public String name() { return "Chicken Burger"; } public float price() { return 50.5f; } } class Coke extends ColdDrink { public String name() { return "Coke"; } public float price() { return 30f; } } class Pepsi extends ColdDrink { public String name() { return "Pepsi"; } public float price() { return 35f; } }
class Meal { private List<Item> items = new ArrayList<Item>(); public void addItem(Item item) { items.add(item); } public float getCost() { float cost = 0.0f; for (Item item : items) cost += item.price(); return cost; } public void showItems() { for (Item item : items) System.out.println("Item:" + item.name() + ", Packing:" + item.packing().pack() + ", Price:" + item.price()); } }
class MealBuilder { public Meal papareVegMeal() { Meal meal = new Meal(); meal.addItem(new VegBurger()); meal.addItem(new Coke()); return meal; } public Meal papareNonVegMeal() { Meal meal = new Meal(); meal.addItem(new ChickenBurger()); meal.addItem(new Pepsi()); return meal; } }
public class BuilderPattern { public static void main(String[] args) { MealBuilder mealBuilder = new MealBuilder();
Meal vegMeal = mealBuilder.papareVegMeal(); System.out.println("Veg Meal"); vegMeal.showItems(); System.out.println("Total cost:" + vegMeal.getCost()); Meal nonVegMeal = mealBuilder.papareNonVegMeal(); System.out.println("\n\nNon-Veg Meal"); nonVegMeal.showItems(); System.out.println("Total cost:" + nonVegMeal.getCost()); } }
|
建造者者模式和工厂模式有什么区别呢?我认为建造者模式更注重于流程,过程更加复杂但是对于流程控制严格的应用场合相当重要。而工厂模式关注与具体的实现策略,讲究到底是如何实现的。
使用建造者模式的优势如下:
- 使用建造者模式可以使客户端不必知道产品内部组成的细节。
- 具体的建造者类之间是相互独立的,对系统的扩展非常有利。
- 由于具体的建造者是独立的,因此可以对建造过程逐步细化,而不对其他的模块产生任何影响。
参考设计模式——建造者
原型模式
原型模式用于创建重复的对象,同时又保证性能。原型模式的思想是生成一个原对象的克隆版本。一般是通过原有对象的内部克隆方法,来生产一个新的对象。相对于工厂、建造者模式,原型模式不需要new一个对象出来,而是直接复制。
原型模式主要应用在直接创建对象的代价比较大的场合。比如,一个对象在一个高代价数据库操作后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,直到需要的时候更新数据库,以此来减少数据库的调用。
原型模式绕过了构造函数,不需要创建实例,使得创建对象就像我们复制粘贴一样easy。而且使用的clone方法是本地方法,可以直接操作内存中的二进制流,性能得到显著提升。但原型模式无视构造方法,与隐藏构造方法的单例模式冲突,使用时要加以注意。此外,复制的过程中需要注意深复制和浅复制,要求我们加以注意。
原型模式的使用方法:
- 实现Cloneable接口。在java语言有一个Cloneable接口,它的作用只有一个,就是在运行时通知虚拟机可以安全地在实现了此接口的类上使用clone方法。在java虚拟机中,只有实现了这个接口的类才可以被拷贝,否则在运行时会抛出CloneNotSupportedException异常。
- 重写Object类中的clone方法。Java中,所有类的父类都是Object类,Object类中有一个clone方法,作用是返回对象的一个拷贝,但是其作用域protected类型的,一般的类无法调用,因此,Prototype类需要将clone方法的作用域修改为public类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
| abstract class PrototypeShape implements Cloneable { private String id; protected String type;
abstract void draw(); public String getType() { return type; } public String getId() { return id; } public void setId(String id) { this.id = id; } public Object clone() { Object clone = null; try { clone = super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return clone; } } class PrototypeRectangle extends PrototypeShape { void draw() { System.out.println("this is rectangle"); } public PrototypeRectangle() { type = "Rectangle"; } } class PrototypeSqure extends PrototypeShape { void draw() { System.out.println("this is squre"); } public PrototypeSqure() { type = "Squre"; } } class PrototypeCircle extends PrototypeShape { void draw() { System.out.println("this is circle"); } public PrototypeCircle() { type = "Circle"; } }
class ShapeCache { private static Map<String, PrototypeShape> shapeMap = new HashMap<String, PrototypeShape>(); public static PrototypeShape getShape(String shapeId) { PrototypeShape cachedShape = shapeMap.get(shapeId); return (PrototypeShape) cachedShape.clone(); } public static void loadCache() { PrototypeCircle circle = new PrototypeCircle(); circle.setId("1"); shapeMap.put(circle.getId(), circle); PrototypeSqure squre = new PrototypeSqure(); squre.setId("2"); shapeMap.put(squre.getId(), squre); PrototypeRectangle rectangle = new PrototypeRectangle(); rectangle.setId("3"); shapeMap.put(rectangle.getId(), rectangle); } }
public class PrototypeDemo { public static void main(String[] args) { ShapeCache.loadCache(); PrototypeShape clonedShape1 = (PrototypeShape) ShapeCache.getShape("1"); System.out.println("Shape:" + clonedShape1.getType()); PrototypeShape clonedShape2 = (PrototypeShape) ShapeCache.getShape("2"); System.out.println("Shape:" + clonedShape2.getType()); PrototypeShape clonedShape3 = (PrototypeShape) ShapeCache.getShape("3"); System.out.println("Shape:" + clonedShape3.getType()); } }
|