设计模式是开发过程中常见的编程技巧,常见的三大类设计模式:结构型设计模式、创建型设计模式和行为型设计模式。下面将详细总结一下各大类中常用的设计模式。
定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
——《Head First设计模式》
假设我们要开发一个绘图程序,用来绘制简单的几何图形,这个软件应该能够处理下面的几种几何对象:圆形(Circle)、矩形(Rectangle)和正方形(Square)。除了各自特有的属性和方法之外,所有的几何图形几乎都可以抽象出绘制(draw)和擦除(erase)两个公共方法,用工厂方法模式进行设计。
使用工厂方法模式:新建CreatCircleFactory、CreatSquareFactory、CreatRectangleFactory类具体工厂继承并实现抽象工厂,负责具体生产Circle、Square、rectangle类。将具体的产品对象创建人物交给具体工厂子类。
UML类图:
public abstract class PaintAppFactoryMethod {
public abstract AbstractPaint PaintShape();
}
abstract class AbstractPaint extends PaintAppFactoryMethod{
public abstract AbstractPaint PaintShape();
}
//concreatFactory Circle
class PaintCircleFactory extends AbstractPaint{
@Override
public AbstractPaint PaintShape() {
return new Circle();
}
}
//concreatFactory Squaree
class PaintSquareFactory extends AbstractPaint{
@Override
public AbstractPaint PaintShape() {
return new Square();
}
}
//concreatFactory Rectangle
class PaintRectangleFactory extends AbstractPaint{
@Override
public AbstractPaint PaintShape() {
return new Rectangle();
}
}
class Rectangle extends PaintRectangleFactory{
float longsidelength=20;
float shortsidelength=10;
public Rectangle(){
System.out.println("draw a Rectangle which longsidelength is"+longsidelength+"and shortsidelength is"+shortsidelength);
}
}
class Square extends PaintSquareFactory{
float sidelength=15;
public Square(){
System.out.println("draw a Circle which sidelength is"+sidelength);
}
}
class Circle extends PaintCircleFactory{
float radius=10;
public Circle(){
System.out.println("draw a Circle which radius is"+radius);
}
}
这是用工厂方法创建图形的一个实例,工厂方法模式可以说在你能想到的开源框架源码中必定会使用的一个设计模式,因为开源框架很重要一点就是要有扩展性,而工厂方法模式恰恰具有可扩展性。弄懂了工厂方法模式,以后看开源代码就很得心应手啦。
优点:
•在工厂方法模式中,工厂方法用来创建客户所需要的产品,同时还向客户隐藏了哪种具体产品类将被实例化这一细节,用户只需要关心所需产品对应的工厂,无须关心创建细节,甚至无须知道具体产品类的类名。
•基于工厂角色和产品角色的多态性设计是工厂方法模式的关键。它能够使工厂可以自主确定创建何种产品对象,而如何创建这个对象的细节则完全封装在具体工厂内部。工厂方法模式之所以又被称为多态工厂模式,是因为所有的具体工厂类都具有同一抽象父类。
•使用工厂方法模式的另一个优点是在系统中加入新产品时,无须修改抽象工厂和抽象产品提供的接口,无须修改客户端,也无须修改其他的具体工厂和具体产品,而只要添加一个具体工厂和具体产品就可以了。这样,系统的可扩展性也就变得非常好,完全符合“开闭原则”。
缺点:
•在添加新产品时,需要编写新的具体产品类,而且还要提供与之对应的具体工厂类,系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,有更多的类需要编译和运行,会给系统带来一些额外的开销。
•由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度,且在实现时可能需要用到DOM、反射等技术,增加了系统的实现难度。
抽象工厂提供一个接口,用于创建相关或依赖对象的家族,而不需要明确的指定具体类。——《Head First设计模式》
说到了工厂方法模式就不可避免的要提到抽象工厂模式,由于工厂方法模式存在创建的工厂子类过多等缺陷,我们引进了抽象工厂的概念。抽象工厂模式与工厂方法模式最大的区别在于,工厂方法模式针对的是一个产品等级结构,而抽象工厂模式则需要面对多个产品等级结构,一个工厂等级结构可以负责多个不同产品等级结构中的产品对象的创建 。当一个工厂等级结构可以创建出分属于不同产品等级结构的一个产品族中的所有对象时,抽象工厂模式比工厂方法模式更为简单、有效率。简单的说:工厂方法是在解决一个产品多个层级方面的事情;而抽象工厂致力于解决多个产品多个层级方面的事情。
举个例子:汽车是由很多零件组成的,比如引擎、轮胎、方向盘等等。现在如果我们是轮胎生产方,要生产宝马轮胎和奔驰轮胎,要用工厂方法还是抽象工厂实现呢?答案是:工厂方法。轮胎是一个产品,宝马轮胎和奔驰轮胎是 2 个不同层级的轮胎,所以用工厂方法解决就足够。假如现在我们是汽车生产方,要生产宝马汽车和奔驰汽车,汽车又包含轮胎和方向盘等等,要用哪个来实现?既然是上面的是工厂方法,那这个就用抽象工厂,因为这涉及到多个产品(轮胎、方向盘等等)和 2 个层级(宝马和奔驰)。
下面看具体示例:
public class AbstractFactoryTest {
public static void main(String[] args) {
// 宝马员工安装轮胎和方向盘
AbstractCarFactory bmwCarFacatory = new BMWCarFactory();
bmwCarFacatory.installWheel();
bmwCarFacatory.installSteeringWheel();
// 奔驰员工安装轮胎和方向盘
AbstractCarFactory mercedesCarFacatory = new MercedesCarFacatory();
mercedesCarFacatory.installWheel();
mercedesCarFacatory.installSteeringWheel();
}
}
/**
* 汽车抽象工厂
*/
interface AbstractCarFactory {
void installWheel();
void installSteeringWheel();
}
/**
* 宝马工厂
*/
class BMWCarFactory implements AbstractCarFactory {
@Override
public void installWheel() {
WheelFacatory wheelFacatory = new BMWWheelFacatory();
String wheel = wheelFacatory.createWheel();
System.out.println("安装轮胎:" + wheel);
}
@Override
public void installSteeringWheel() {
SteeringWheelFacatory steeringWheelFacatory = new BMWSteeringWheelFacatory();
String steeringWheel = steeringWheelFacatory.createSteeringWheel();
System.out.println("安装方向盘:" + steeringWheel);
}
}
/**
* 奔驰工厂
*/
class MercedesCarFacatory implements AbstractCarFactory {
@Override
public void installWheel() {
WheelFacatory wheelFacatory = new MercedesWheelFacatory();
String wheel = wheelFacatory.createWheel();
System.out.println("安装轮胎:" + wheel);
}
@Override
public void installSteeringWheel() {
SteeringWheelFacatory steeringWheelFacatory = new MercedesSteeringWheelFacatory();
String steeringWheel = steeringWheelFacatory.createSteeringWheel();
System.out.println("安装方向盘:" + steeringWheel);
}
}
/**
* 轮胎工厂
*/
interface WheelFacatory {
String createWheel();
}
/**
* 宝马轮胎工厂
*/
class BMWWheelFacatory implements WheelFacatory {
@Override
public String createWheel() {
System.out.println("宝马轮胎工厂生产轮胎");
return "宝马轮胎";
}
}
/**
* 奔驰轮胎工厂
*/
class MercedesWheelFacatory implements WheelFacatory {
@Override
public String createWheel() {
System.out.println("奔驰轮胎工厂生产轮胎");
return "奔驰轮胎";
}
}
/**
* 方向盘工厂
*/
interface SteeringWheelFacatory {
String createSteeringWheel();
}
/**
* 宝马方向盘工厂
*/
class BMWSteeringWheelFacatory implements SteeringWheelFacatory {
@Override
public String createSteeringWheel() {
System.out.println("宝马方向盘工厂生产方向盘");
return "宝马方向盘";
}
}
/**
* 奔驰方向盘工厂
*/
class MercedesSteeringWheelFacatory implements SteeringWheelFacatory {
@Override
public String createSteeringWheel() {
System.out.println("奔驰方向盘工厂生产方向盘");
return "奔驰方向盘";
}
}
简单来说:结合了抽象工厂和工厂方法模式,由车厂类来负责产品族即整车的生产,由具体工厂子类负责部件生产。
优点:
•抽象工厂模式隔离了具体类的生成,使得客户并不需要知道什么被创建。由于这种隔离,更换一个具体工厂就变得相对容易。所有的具体工厂都实现了抽象工厂中定义的那些公共接口,因此只需改变具体工厂的实例,就可以在某种程度上改变整个软件系统的行为。另外,应用抽象工厂模式可以实现高内聚低耦合的设计目的,因此抽象工厂模式得到了广泛的应用。
•当一个产品族中的多个对象被设计成一起工作时,它能够保证客户端始终只使用同一个产品族中的对象。这对一些需要根据当前环境来决定其行为的软件系统来说,是一种非常实用的设计模式。
•增加新的具体工厂和产品族很方便,无须修改已有系统,符合“开闭原则”。
缺点:
•在添加新的产品对象时,难以扩展抽象工厂来生产新种类的产品,这是因为在抽象工厂角色中规定了所有可能被创建的产品集合,要支持新种类的产品就意味着要对该接口进行扩展,而这将涉及到对抽象工厂角色及其所有子类的修改,显然会带来较大的不便。
•开闭原则的倾斜性(增加新的工厂和产品族容易,增加新的产品等级结构麻烦)。
建造者模式(Builder Pattern):将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
建造者模式的使用场景是:创建复杂的对象。什么才能算复杂对象?如果一个对象只需要通过 new XXX() 的方式创建,那就算是一个简单对象;如果需要 new XXX(),并且还要设置很多属性,那这种就可以称为复杂对象了,因为它的构建过程比较复杂。采用建造者模式,可以把这个复杂的构建过程抽离开,使它不依赖创建者。
下面通过一个示例:
在游戏中,要求设计一个程序来画小人(person),要求小人有头(head)、身体(body)、两手(arm)、两脚(leg)就可以了,小人有高、矮之分,利用建造者模式进行设计。
使用创建者模式进行实现,由Director作为指挥类,由抽象创建者Builder指定抽象创建方法,由其子类concreteBuilder类进行具体创建,Person类为所创建的具体产品类,实现小人的具体属性。
UML类图:
public class Director {
private Builder builder;
public Director(Builder builder)
{
this.builder=builder;
}
public Person construct() {
builder.buildArms();
builder.buildBody();
builder.buildHead();
builder.buildLegs();
builder.setHeight();
return builder.getResult();
}
}
abstract class Builder{
protected Person person=new Person();
public abstract void buildHead();
public abstract void buildBody();
public abstract void buildArms();
public abstract void buildLegs();
public abstract void setHeight();
public Person getResult(){
return person;
}
}
class concreteBuilder extends Builder{
@Override
public void buildHead() {
person.setHead("This is a head、");
}
public void buildBody(){
person.setBody("This is a body、");
}
public void buildArms(){
person.setArms("These are tow arms、");
}
public void buildLegs(){
person.setLegs("These are two legs,");
}
public void setHeight(){
double radom=Math.random();
if(radom>=0.5) {
person.setHeight("tall");
}
else {
person.setHeight("short");
}
}
}
class Person{
private String head;
private String body;
private String arms;
private String legs;
private String height;
//Setter
public void setHead(String head){
this.head=head;
}
public void setBody(String body){
this.body=body;
}
public void setArms(String arms){
this.arms=arms;
}
public void setLegs(String legs){
this.legs=legs;
}
public void setHeight(String height){
this.height=height;
}
//Getter
public String getHead(){
return head;
}
public String getBody(){
return body;
}
public String getArms(){
return arms;
}
public String getLegs(){
return legs;
}
public String getHeight(){
return height;
}
public void showMessage(){
System.out.println("I am a person "+getHead()+
getBody()+
getArms()+
getLegs()+"and I am "+
getHeight());
}
}
可以看到,我们在Director中通过调用具体建造者来完成具体部分的创建于设置,实现解耦。一方面它隔离了客户与生产过程;另一方面它负责控制产品的生成过程。指挥者针对抽象建造者编程,客户端只需要知道具体建造者的类型,即可通过指挥者类调用建造者的相关方法,返回一个完整的产品对象。
优点:
•在建造者模式中,客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
•每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者,用户使用不同的具体建造者即可得到不同的产品对象。
•可以更加精细地控制产品的创建过程。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。
•增加新的具体建造者无须修改原有类库的代码,指挥者类针对抽象建造者类编程,系统扩展方便,符合“开闭原则”。
缺点:
•建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
•如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大。
总之,通过建造者模式,可以把本来强依赖的东西解绑,不仅仅解决依赖问题,还提高了封装性,让使用者不用明白内部的细节。
单例模式确保一个类只有一个实例,并提供一个全局访问点。 ——《Head First设计模式》
对于系统中的某些类来说,只有一个实例很重要,例如,一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务;一个系统只能有一个窗口管理器或文件系统;一个系统只能有一个计时工具或ID(序号)生成器。
单例模式的要点有三个:一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。单例模式是一种对象创建型模式。单例模式又名单件模式或单态模式。
懒汉型单例模式:
创建唯一对象的方法在这里有两种,一种是由单例类本身事先创建完成,系统需要用时直接取用计科,但由于我们并不知道我们什么时候需要用到这个单例(或许永远不会用到)因此我们也可以在系统需要这个实例的时候去创建这个单例:这就叫懒汉型的创建单例。如下代码:
public class Singleton
{
private static Singleton instance=null; //静态私有成员变量
//私有构造函数
private Singleton()
{
}
//静态公有工厂方法,返回唯一实例
public static Singleton getInstance()
{
if(instance==null)
instance=new Singleton();
return instance;
}
}
可以看到,由于确保单例的需要,我们需要一个私有的构造函数,确保只有类本身可以调用该函数创建对象,当系统需要该对象时,通过getInstance方法来取得对象。在取用时,需要判断当前对象是否已经存在,若存在则不再创建。
在单线程运行下,这个写法不会有任何问题,然而,在多线程并发的情况下,则可能会遇到较为尴尬的局面。试想,当线程A判断instance==null为true时,若此时线程阻塞,即还未执行new Singleton()创建实例,而线程B此时进入,由于此时系统中尚未创建该实例,其判断instance==null也为true,并创建一个实例,此时线程A恢复执行,也创建一个实例,那么系统中将存在两个实例,单例被打破。
此时有一种解决方法:只要将getInstance()方法变成同步方法,那么这个现象就可解决了。如下:
public class Singleton
{
private static synchronized Singleton instance=null; //静态私有成员变量
//私有构造函数
private Singleton()
{
}
//静态公有工厂方法,返回唯一实例
public static Singleton getInstance()
{
if(instance==null)
instance=new Singleton();
return instance;
}
}
然而,问题显然没有这么简单,我们知道,获取实例的方法只在第一次调用时可能发生上述情况,后续过程中只要实例存在,那么就不会再出现上述局面,也就是说,同步的getInstance()方法我们是需要在第一次执行时调用即可,而上述处理方式每次都将调用,我们都知道synchronized内置锁是重量级锁,将使我们方法的执行效率降低100倍。每次都调用的情况下对性能的消耗是我们无法容忍的,那么怎么解决这个问题呢?
1、倘若你的程序能够接受这样的效率损失,即可预知这个方法调用的次数不多。那么久什么都不做即可。
2、使用双重加锁的方法。
首先检查实例是否已经创建,如果尚未创建,才进行同步。这样一来,只有第一次创建时才进行同步。
代码如下:
public class Singleton
{
private static volatile Singleton instance; //静态私有成员变量
//私有构造函数
private Singleton()
{
}
//静态公有工厂方法,返回唯一实例
public static Singleton getInstance()
{ if(instance==null){
synchronized(Singleton.class){
if(instance==null)
instance=new Singleton();
}
}
return instance;
}
}
3、使用饿汉式的单例模式
在类中直接创建好实例。如下:
public class Singleton
{
private static Singleton instance=new Singleton(); //静态私有成员变量
//私有构造函数
private Singleton()
{
}
//静态公有工厂方法,返回唯一实例
public static Singleton getInstance()
{
return instance;
}
}
优点:
•提供了对唯一实例的受控访问。因为单例类封装了它的唯一实例,所以它可以严格控制客户怎样以及何时访问它,并为设计及开发团队提供了共享的概念。
•由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象,单例模式无疑可以提高系统的性能。
•允许可变数目的实例。我们可以基于单例模式进行扩展,使用与单例控制相似的方法来获得指定个数的对象实例。
缺点:
•由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。
•单例类的职责过重,在一定程度上违背了“单一职责原则”。因为单例类既充当了工厂角色,提供了工厂方法,同时又充当了产品角色,包含一些业务方法,将产品的创建和产品的本身的功能融合到一起。
•滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;现在很多面向对象语言(如Java、C#)的运行环境都提供了自动垃圾回收的技术,因此,如果实例化的对象长时间不被利用,系统会认为它是垃圾,会自动销毁并回收资源,下次利用时又将重新实例化,这将导致对象状态的丢失。
原型模式(Prototype Pattern):原型模式是一种对象创建型模式,用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象。原型模式允许一个对象再创建另外一个可定制的对象,无须知道任何创建的细节。
在原型模式结构中定义了一个抽象原型类,所有的Java类都继承自java.lang.Object,而Object类提供一个clone()方法,可以将一个Java对象复制一份。因此在Java中可以直接使用Object提供的clone()方法来实现对象的克隆。
实例:
public class PrototypeDemo implements Cloneable
{
……
public Object clone()
{
Object object = null;
try {
object = super.clone();
} catch (CloneNotSupportedException exception) {
System.err.println("Not support cloneable");
}
return object;
}
……
}
一个类包含一些成员对象,在使用原型模式克隆对象时,根据其成员对象是否也克隆,原型模式可以分为两种形式:深克隆和浅克隆。
原型模式的优点
•当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程,通过一个已有实例可以提高新实例的创建效率。
•可以动态增加或减少产品类。
•原型模式提供了简化的创建结构。
•可以使用深克隆的方式保存对象的状态。
原型模式的缺点
•需要为每一个类配备一个克隆方法,而且这个克隆方法需要对类的功能进行通盘考虑,这对全新的类来说不是很难,但对已有的类进行改造时,不一定是件容易的事,必须修改其源代码,违背了“开闭原则”。
•在实现深克隆时需要编写较为复杂的代码。
行为型模式为设计模式的一种类型,行为型模式分为类行为型模式和对象行为型模式两种:
•类行为型模式:类的行为型模式使用继承关系在几个类之间分配行为,类行为型模式主要通过多态等方式来分配父类与子类的职责。
•对象行为型模式:对象的行为型模式则使用对象的聚合关联关系来分配行为,对象行为型模式主要是通过对象关联等方式来分配两个或多个类的职责。根据“合成复用原则”,系统中要尽量使用关联关系来取代继承关系,因此大部分行为型设计模式都属于对象行为型设计模式。
行为型设计模式用来识别对象之间的常用交流模式并加以实现。如此,可在进行这些交流活动时增强弹性。常见的有11种类型的模式,包括模板方法模式、中介者模式、命令模式、职责链模式、策略模式、观察者模式、迭代器模式、状态模式、解释器模式、访问者模式、备忘录模式。下面着重总结几种。
定义一个操作中的算法的框架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。 ——《Head First设计模式》
模板方法模式是基于继承的代码复用基本技术,模板方法模式的结构和用法也是面向对象设计的核心之一。在模板方法模式中,可以将相同的代码放在父类中,而将不同的方法实现放在不同的子类中。
在模板方法模式中,我们需要准备一个抽象类,将部分逻辑以具体方法以及具体构造函数的形式实现,然后声明一些抽象方法来让子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现,这就是模板方法模式的用意。模板方法模式体现了面向对象的诸多重要思想,是一种使用频率较高的模式。
下面通过一个示例说明:
一双鞋子从表面来看,由鞋底、鞋垫、鞋面、鞋带组成,同一系列的鞋子这几个部分都是一样的,用同样的材料做出来,不同系列的鞋子就大相径庭了。根据模板方法模式,组装一双鞋子的制造过程可以归并为固定的框架,至于用什么材料,那由每个系列的鞋子去具体实现。我们先看定义组装鞋子的框架代码。
/**
* 定义鞋子制造的工序框架
*/
abstract class ShoeInstallTemplate {
public abstract void installSole();
public abstract void installInsole();
public abstract void installVamp();
public abstract void installShoelace();
public void installShot(){
System.out.println("组装一双鞋,步骤如下:");
// 组装鞋底
installSole();
// 组装鞋垫
installInsole();
// 组装鞋面
installVamp();
// 组装鞋带
installShoelace();
}
}
定义了一个组装鞋子框架的抽象类 ShoeInstallTemplate,里面有 4 个工序未具体实现,由鞋子制造商去实现,因为只有鞋子制造商才知道鞋子要用什么材料来做。
下面举 2 个比较出名的鞋子:Adidas 的 Boost 系列和 Nike 的 Jordan 系列。下面分别实现这 2 个系列鞋子的制造代码。
/**
* Adidas Boost 鞋制造
*/
class AdidasBoostShoeInstall extends ShoeInstallTemplate {
@Override
public void installSole() {
System.out.println("组装白色 Boost 鞋底");
}
@Override
public void installInsole() {
System.out.println("组装黑色 Boost 鞋垫");
}
@Override
public void installVamp() {
System.out.println("组装黑色 Boost 鞋面");
}
@Override
public void installShoelace() {
System.out.println("组装黑色 Boost 鞋带");
}
}
/**
* Nike Jordan 鞋制造
*/
class NikeJordanShoeInstall extends ShoeInstallTemplate {
@Override
public void installSole() {
System.out.println("组装黑色 Jordan 鞋底");
}
@Override
public void installInsole() {
System.out.println("组装黑色 Jordan 鞋垫");
}
@Override
public void installVamp() {
System.out.println("组装红色 Jordan 鞋面");
}
@Override
public void installShoelace() {
System.out.println("组装红色 Jordan 鞋带");
}
}
模板方法是一个比较实用的模式,为什么说实用呢?举个现实的例子,Java 能有如今的发展,离不开各大开源框架,比如 Spring,看过源码就知道,里面大量代码运用了模板方法设计模式。掌握好模板方法,对读源码有非常大的帮助,很多人觉得这些代码那么绕?调来调去的。当你了解了常用的设计模式之后,看源代码就可以很容易的知道是用什么设计模式,为什么用这个设计模式?有了这层思考,就像有一条线将以前散落在各地的知识点连接起来,成了可以推敲的知识。
模板方法中还有一个称为钩子的方法,这是一个默认不做事的方法,子类视情况需不需要覆盖这个方法。他可作为条件控制的手段,这里不展开详细说明,
优点:
•模板方法模式在一个类中形式化地定义算法,而由它的子类实现细节的处理。
•模板方法模式是一种代码复用的基本技术。
•模板方法模式导致一种反向的控制结构,通过一个父类调用其子类的操作,通过对子类的扩展增加新的行为,符合“开闭原则”。
缺点:
每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象,但是更加符合“单一职责原则”,使得类的内聚性得以提高。
将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。——《Head First设计模式》
命令模式可以对发送者和接收者完全解耦,发送者与接收者之间没有直接引用关系,发送请求的对象只需要知道如何发送请求,而不必知道如何完成请求。这就是命令模式的模式动机。
下面通过具体实现来说明:
首先我们需要让所有对象实现包含同一个方法的接口Command:
public interface Command{
public void excuse();
}
假设现在要实现一个打开电灯的命令,根据厂家提供的电灯Light类:
class Light{
public void on(){
}
public void off(){
}
}
那么开灯命令类:
class LightOnCommand implements Command{
Light light;
public LightOnCommand(Light light) {
this.light = light;
}
@Override
public void excute() {
light.on();
}
}
由于Light类被传入具体命令类中,因此命令类可实现对Light类的控制。即只需要调用excute命令,电灯Light类作为接受者,接受Command传来的命令并执行,实现命令类与实现类的解耦。也就是使用命令类来传递指令。
优点:
•降低系统的耦合度。
•新的命令可以很容易地加入到系统中。
•可以比较容易地设计一个命令队列和宏命令(组合命令)。
•可以方便地实现对请求的Undo和Redo。
缺点:
•使用命令模式可能会导致某些系统有过多的具体命令类。因为针对每一个命令都需要设计一个具体命令类,因此某些系统可能需要大量具体命令类,这将影响命令模式的使用。
策略模式定义了算法族,分别封装起来,让他们之间可以互相替换,此模式让算法的变换独立于使用算法的用户。——《Head First设计模式》
策略模式是对算法的封装,它把算法的责任和算法本身分割开,委派给不同的对象管理。策略模式通常把一个系列的算法封装到一系列的策略类里面,作为一个抽象策略类的子类。用一句话来说,就是“准备一组算法,并将每一个算法封装起来,使得它们可以互换”。
下面通过实例说明:
假设设计一个网上书店,该系统中所有的计算机类图书(ComputerBook)每本都有10%的折扣,所有的语言类图书(LanguageBook)每本都有2元的折扣,小说类图书(NovelBook)每100元有10元的折扣。现使用策略模式来设计该系统。
那么可以设计一抽象策略类Strategy,定义抽象方法onSale()。具体折扣类ComputerBook,Language,NovelBook继承并实现Strategy类中的折扣方法。定义环境类Context,在环境类中调用具体折扣类。
UML类图:
abstract public class Strategy {
abstract public void onSale();
}
class ComputerBooks extends Strategy{
@Override
public void onSale() {
System.out.println("每本书有10%折扣!");
}
}
class LanguageBooks extends Strategy{
@Override
public void onSale() {
System.out.println("每本书有2元折扣!");
}
}
class NovelBooks extends Strategy{
@Override
public void onSale() {
System.out.println("每本书有10元折扣!");
}
}
class Context{
Strategy strategy;
public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}
public void onSale(){
strategy.onSale();
}
}
其实可以发现,策略模式和前面所讲的模板方法模式其实非常类似,两者一个重要的不同在于,策略模式实现了整个算法的过程,而模板方法仅仅是作为整体算法的一个部分。
优点:
•策略模式提供了对“开闭原则”的完美支持,用户可以在不修改原有系统的基础上选择算法或行为,也可以灵活地增加新的算法或行为。
•策略模式提供了管理相关的算法族的办法。
•策略模式提供了可以替换继承关系的办法。
•使用策略模式可以避免使用多重条件转移语句。
缺点:
•客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
•策略模式将造成产生很多策略类,可以通过使用享元模式在一定程度上减少对象的数量。
使多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止。——《Head First设计模式》
行为型模式(Behavioral Pattern)是对在不同的对象之间划分责任和算法的抽象化。
行为型模式不仅仅关注类和对象的结构,而且重点关注它们之间的相互作用。
通过行为型模式,可以更加清晰地划分类与对象的职责,并研究系统在运行时实例对象之间的交互。在系统运行时,对象并不是孤立的,它们可以通过相互通信与协作完成某些复杂功能,一个对象在运行时也将影响到其他对象的运行。
职责链模式可以将请求的处理者组织成一条链,并使请求沿着链传递,由链上的处理者对请求进行相应的处理,客户端无须关心请求的处理细节以及请求的传递,只需将请求发送到链上即可,将请求的发送者和请求的处理者解耦。
下面通过实例说明:
某物资管理系统中物资采购需要分级审批,主任可以审批1万元及以下的采购单,部门经理可以审批5万元及以下的采购单,副总经理可以审批10万元及以下的采购单,总经理可以审批20万元及以下的采购单,20万元以上的采购单需要开会确定。现使用职责链模式设计该系统。
设计思路:
设计抽象领导类Leader,具体职责子类Director,PartmentManager,GeneralManager,VicManager类实现业务逻辑。简而言之,即通过创建层级的处理者类来处理请求,当当前处理者无法处理时,交给上一级处理者。
UML类图:
import java.math.BigDecimal;
abstract public class Leader {
protected String name;
protected Leader successor;
public void setSuccessor(Leader successor){
this.successor=successor;
}
public Leader(String name){
this.name=name;
}
abstract public void handlerRequest(Request request);
}
class Director extends Leader{
public Director(String name){
super(name);
}
public void handlerRequest(Request request){
BigDecimal bigDecimal=new BigDecimal(10000.00);
if(request.getCost().compareTo(bigDecimal)==-1||request.getCost().compareTo(bigDecimal)==0){
System.out.println("主任"+name+"审批,采购金额为:"+request.getCost());
}
else {
this.successor.handlerRequest(request);
}
}
}
class PartmentManager extends Leader{
public PartmentManager(String name){
super(name);
}
public void handlerRequest(Request request){
BigDecimal bigDecimal=new BigDecimal(50000.00);
if(request.getCost().compareTo(bigDecimal)==-1||request.getCost().compareTo(bigDecimal)==0){
System.out.println("部门经理"+name+"审批,采购金额为:"+request.getCost());
}
else {
this.successor.handlerRequest(request);
}
}
}
class GeneralManager extends Leader{
public GeneralManager(String name){
super(name);
}
public void handlerRequest(Request request){
BigDecimal bigDecimal=new BigDecimal(100000.00);
if(request.getCost().compareTo(bigDecimal)==-1||request.getCost().compareTo(bigDecimal)==0){
System.out.println("副总经理"+name+"审批,采购金额为:"+request.getCost());
}
else {
this.successor.handlerRequest(request);
}
}
}
class VicManager extends Leader{
public VicManager(String name){
super(name);
}
public void handlerRequest(Request request){
BigDecimal bigDecimal=new BigDecimal(200000.00);
if(request.getCost().compareTo(bigDecimal)==-1||request.getCost().compareTo(bigDecimal)==0){
System.out.println("总经理"+name+"审批,采购金额为:"+request.getCost());
}
else {
System.out.println("采购金额为:"+request.getCost()+" "+"数额过大,需开会决定!");
}
}
}
class Request{
private BigDecimal cost;
public Request(BigDecimal bigDecimal){
this.cost=bigDecimal;
}
public BigDecimal getCost(){
return cost;
}
public void setCost(BigDecimal bigDecimal){
this.cost=bigDecimal;
}
}
并在客户端中设置处理者的层次结构:
import java.math.BigDecimal;
import java.util.ArrayList;
public class TestLeader {
public static void main(String args[]) {
Leader director = new Director("Tom"),
partmentManager = new PartmentManager("Jack"),
generalManager = new GeneralManager("Rose"),
vic = new VicManager("Wang");
director.setSuccessor(partmentManager);
partmentManager.setSuccessor(generalManager);
generalManager.setSuccessor(vic);
Request request=new Request(new BigDecimal(70000.00)),
request1=new Request(new BigDecimal(150000.00)),
request2=new Request(new BigDecimal(210000.00));
director.handlerRequest(request);
director.handlerRequest(request1);
director.handlerRequest(request2);
}
}
如上,通过抽象类中的setSuccessor()方法设置每一个处理者的上一级处理者,当前处理者不满足处理请求的条件时,将请求传递给上一级。
优点:
•降低耦合度
•可简化对象的相互连接
•增强给对象指派职责的灵活性
•增加新的请求处理类很方便
缺点:
•不能保证请求一定被接收。
•系统性能将受到一定影响,而且在进行代码调试时不太方便;可能会造成循环调用。
定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。——《Head First设计模式》
观察者设计模式可以让你熟知现状,不会错过该对象感兴趣的事情,对象甚至可以在运行时决定是否要继续被通知。观察者模式是JDK中使用的最多的模式之一,非常有用。
它建立一种对象与对象之间的依赖关系,一个对象发生改变时将自动通知其他对象,其他对象将相应做出反应。这个交互也称作“发布-订阅”。在此,发生改变的对象称为观察目标,而被通知的对象称为观察者,一个观察目标可以对应多个观察者,而且这些观察者之间没有相互联系,可以根据需要增加和删除观察者,使得系统更易于扩展。
下面通过实例说明:
如果网上商店中商品(product)在名称(name)、价格(price)等方面有变化,系统能自动通知会员,将是网上商店区别传统商店的一大特色。通过观察者模式实现。
设计思路:使用观察者模式。设计抽象观察目标类Subject,具体子类商品类Goods,包含商品名,商品价格的信息。抽象观察者类Observers,具体观察者类Customer1,Customer2。
import java.math.BigDecimal;
import java.util.ArrayList;
abstract public class Subject {
String goodsName;
BigDecimal goodsPrice;
ArrayList arrayList=new ArrayList<>();
public void addObserver(Observers observers){
arrayList.add(observers);
}
public void removeObserver(Observers observers){
arrayList.remove(observers);
}
public void notifys(){
for(Observers observers:arrayList){
observers.updtae();
}
}
}
class Goods extends Subject{
public void setGoodsName(String goodsName){
this.goodsName=goodsName;
}
public String getGoodsName(){
return goodsName;
}
public void setGoodsPrice(BigDecimal goodsPrice){
this.goodsPrice=goodsPrice;
}
public BigDecimal getGoodsPrice(){
return goodsPrice;
}
}
abstract class Observers{
String goodsName;
BigDecimal goodsPrice;
Goods goods=new Goods();
abstract public void updtae();
}
class Customer1 extends Observers{
@Override
public void updtae() {
this.goodsName=goods.getGoodsName();
this.goodsPrice=goods.getGoodsPrice();
}
public void showMessage(){
System.out.println(this.goodsName);
System.out.println(this.goodsPrice);
}
}
class Customer2 extends Observers{
@Override
public void updtae() {
this.goodsName=goods.getGoodsName();
this.goodsPrice=goods.getGoodsPrice();
}
}
客户端中使用如下:
import java.math.BigDecimal;
public class TestSubject {
public static void main(String[] args) {
Subject goods=new Goods();
goods.addObserver(new Customer1());
goods.addObserver(new Customer2());
((Goods) goods).setGoodsName("牛奶");
((Goods) goods).setGoodsPrice(new BigDecimal(3.5));
goods.notifys();
Observers observers=new Customer1();
((Customer1) observers).showMessage();
}
}
我们在客户端中进行观察者的注册,当某些观察者不需要再接受更新信息时,则可从观察者队列中去除。
优点:
•观察者模式可以实现表示层和数据逻辑层的分离,并定义了稳定的消息更新传递机制,抽象了更新接口,使得可以有各种各样不同的表示层作为具体观察者角色。
•观察者模式在观察目标和观察者之间建立一个抽象的耦合。
•观察者模式支持广播通信。
观察者模式符合“开闭原则”的要求。
缺点:
•如果一个观察目标对象有很多直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
•如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
•观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
观察者模式
是一个比较特殊的设计模式,它定义了触发机制,观察者只要订阅了被观察者,就可以第一时间得到被观察者传递的信息。在工作中,使用观察者模式的场景也比较多,比如消息队列消费,Android 开发中的事件触发机制等等。
状态模式允许对象在内部状态改变时改变他的行为,对象看起来好像修改了它的类。——《Head First设计模式》
很显然,从UML类图看来,状态模式和策略模式完全一样,但是两个模式主要的区别在于他们的意图。
以状态模式而言,我们将一群对象的行为封装在一个状态中,context中的行为随时可以委托到这些状态对象中的一个,随着时间流逝,当前状态在状态的集合中游走改变,以反映出context中的状态,因此,context中的行为会跟着改变。但是客户对于状态的改变并不了解。
而策略模式来说,客户端通常指定要组合的策略对象,对于某个context对象,通常都只有一个最合适的策略对象。
状态模式描述了对象状态的变化以及对象如何在每一种状态下表现出不同的行为。
状态模式的关键是引入了一个抽象类来专门表示对象的状态,这个类我们叫做抽象状态类,而对象的每一种具体状态类都继承了该类,并在不同具体状态类中实现了不同状态的行为,包括各种状态之间的转换。
下面通过实例分析:
一部手机,在其接受到来电的时候,会发出声音来提醒主人。而现在我们需要为该手机添加一项功能,在接收来电的时候,产生震动,为令其更加高级,不仅发声,而且振动,而且有灯光闪烁,以状态模式设计。
设计思路:用状态模式进行实现,设计环境类ModeSelecter,抽象状态类Mode,具体状态子类RingOnly,RingAndShake,RingAndShakeAndTwinkling。
UML类图:
public class ModeSelecter {
Mode mode;
public void setMode(Mode mode) {
this.mode = mode;
}
}
abstract class Mode{
abstract public void handle();
public void ring(){
System.out.println("ring......ring......");
}
abstract public void ringAndShake();
abstract public void ringAndShakeAndTwinkling();
}
class RingOnly extends Mode{
@Override
public void handle() {
ring();
}
@Override
public void ringAndShake() {
}
@Override
public void ringAndShakeAndTwinkling() {
}
}
class RingAndShake extends Mode{
@Override
public void handle() {
ringAndShake();
}
@Override
public void ringAndShake() {
System.out.println("ringing and shaking......");
}
@Override
public void ringAndShakeAndTwinkling() {
}
}
class RingAndShakeAndTwinkling extends Mode{
@Override
public void handle() {
ringAndShakeAndTwinkling();
}
@Override
public void ringAndShake() {
}
@Override
public void ringAndShakeAndTwinkling() {
System.out.println("ringing and shaking and twinking......");
}
}
客户端中指定状态:
public class TestMode {
public static void main(String[] args) {
ModeSelecter modeSelecter=new ModeSelecter();
modeSelecter.setMode(new RingOnly());
modeSelecter.mode.handle();
System.out.println("---------------------------------");
modeSelecter.setMode(new RingAndShake());
modeSelecter.mode.handle();
System.out.println("---------------------------------");
modeSelecter.setMode(new RingAndShakeAndTwinkling());
modeSelecter.mode.handle();
}
}
优点:
•封装了转换规则。
•枚举可能的状态,在枚举状态之前需要确定状态种类。
•将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。
•允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。
•可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。
缺点:
•状态模式的使用必然会增加系统类和对象的个数。
•状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。
•状态模式对“开闭原则”的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态;而且修改某个状态类的行为也需修改对应类的源代码。
(其他行为型设计模式有空再写............)
结构型模式(Structural Pattern)描述如何将类或者对象结合在一起形成更大的结构,就像搭积木,可以通过简单积木的组合形成复杂的、功能更为强大的结构。
结构型模式可以分为类结构型模式和对象结构型模式:
•类结构型模式关心类的组合,由多个类可以组合成一个更大的系统,在类结构型模式中一般只存在继承关系和实现关系。
•对象结构型模式关心类与对象的组合,通过关联关系使得在一个类中定义另一个类的实例对象,然后通过该对象调用其方法。根据“合成复用原则”,在系统中尽量使用关联关系来替代继承关系,因此大部分结构型模式都是对象结构型模式。
常用的结构型设计模式有:适配器模式、桥接模式、享元模式、外观模式、代理模式、装饰模式和组合模式。下面主要介绍几种。
将一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。——《Head First设计模式》
通常情况下,客户端可以通过目标类的接口访问它所提供的服务。有时,现有的类可以满足客户类的功能需要,但是它所提供的接口不一定是客户类所期望的,这可能是因为现有类中方法名与目标类中定义的方法名不一致等原因所导致的。
在这种情况下,现有的接口需要转化为客户类期望的接口,这样保证了对现有类的重用。如果不进行这样的转化,客户类就不能利用现有类所提供的功能,适配器模式可以完成这样的转化。
在适配器模式中可以定义一个包装类,包装不兼容接口的对象,这个包装类指的就是适配器(Adapter),它所包装的对象就是适配者(Adaptee),即被适配的类。
适配器提供客户类需要的接口,适配器的实现就是把客户类的请求转化为对适配者的相应接口的调用。也就是说:当客户类调用适配器的方法时,在适配器类的内部将调用适配者类的方法,而这个过程对客户类是透明的,客户类并不直接访问适配者类。因此,适配器可以使由于接口不兼容而不能交互的类可以一起工作。
下面通过实例说明:
使用Java语言实现一个双向适配器实例,使得猫可以学狗叫,狗可以学猫抓老鼠。
设计思路:使用双向适配器类Adapter,类Cat,Dog都既是目标类也是适配者类。
public class AnimalAdapter implements Dog,Cat {
private Dog dog;
private Cat cat;
public AnimalAdapter(Dog dog){
this.dog=dog;
}
public AnimalAdapter(Cat cat){
this.cat=cat;
}
public void barking(){
cat.catchMouse();
}
public void catchMouse(){
dog.barking();
}
}
interface Dog{
public void barking();
}
interface Cat{
public void catchMouse();
}
class ConcorretDog implements Dog{
public void barking(){
System.out.println("I am a Dog!!!");
}
}
class ConcorrectCat implements Cat{
public void catchMouse(){
System.out.println("I'm catching a mouse!!!");
}
}
优点:
•将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,而无须修改原有代码。
•增加了类的透明性和复用性,将具体的实现封装在适配者类中,对于客户端类来说是透明的,而且提高了适配者的复用性。
•灵活性和扩展性都非常好,通过使用配置文件,可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合“开闭原则”。
缺点:
类适配器模式的缺点如下:
•对于Java、C#等不支持多重继承的语言,一次最多只能适配一个适配者类,而且目标抽象类只能为抽象类,不能为具体类,其使用有一定的局限性,不能将一个适配者类和它的子类都适配到目标接口。
对象适配器模式的缺点如下:
•与类适配器模式相比,要想置换适配者类的方法就不容易。如果一定要置换掉适配者类的一个或多个方法,就只好先做一个适配者类的子类,将适配者类的方法置换掉,然后再把适配者类的子类当做真正的适配者进行适配,实现过程较为复杂。
动态的将责任附加到对象上,若要拓展功能,装饰者提供了比继承更有弹性的替代模式。——《Head First设计模式》
即将一个类的对象嵌入另一个对象中,由另一个对象来决定是否调用嵌入对象的行为以便扩展自己的行为,我们称这个嵌入的对象为装饰器(Decorator)。
装饰模式以对客户透明的方式动态地给一个对象附加上更多的责任,换言之,客户端并不会觉得对象在装饰前和装饰后有什么不同。装饰模式可以在不需要创造更多子类的情况下,将对象的功能加以扩展。
下面通过实例说明:
某图书管理系统中,书籍类(Book)具有借书方法borrowBook()和还书方法returnBook()。现需要动态给书籍对象添加冻结方法freeze()和遗失方法lose()。使用装饰模式设计该系统。
设计思路:设计抽象基类Book类,其中有borrowBook()方法和returnBook()方法。设计装饰类BookDecorator,调用基类的两种方法,并在具体装饰类中加上两种新方法。
UML类图:
public class Book {
protected void borrowBook(){
System.out.println("Borrowing a book......");
}
protected void returnBook(){
System.out.println("Returning a book......");
}
}
class BookDecorator extends Book{
Book book;
public BookDecorator(Book book){
this.book=book;
}
public void borrowBook(){
book.borrowBook();
}
public void returnBook(){
book.returnBook();
}
}
class ConcreteDecorator extends BookDecorator
{
public ConcreteDecorator(Book book)
{
super(book);
}
public void op(){
super.borrowBook();
super.returnBook();
freeze();
lose();
}
public void freeze(){
System.out.println("freezing a book......");
}
public void lose(){
System.out.println("losing a book......");
}
}
优点:
•装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。
•可以通过一种动态的方式来扩展一个对象的功能,通过配置文件可以在运行时选择不同的装饰器,从而实现不同的行为。
•通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合。可以使用多个具体装饰类来装饰同一对象,得到功能更为强大的对象。
•具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,在使用时再对其进行组合,原有代码无须改变,符合“开闭原则”。
缺点:
•使用装饰模式进行系统设计时将产生很多小对象,这些对象的区别在于它们之间相互连接的方式有所不同,而不是它们的类或者属性值有所不同,同时还将产生很多具体装饰类。这些装饰类和小对象的产生将增加系统的复杂度,加大学习与理解的难度。
•这种比继承更加灵活机动的特性,也同时意味着装饰模式比继承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为烦琐。
要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行。门面模式提供一个高层次的接口,使得子系统更易于使用。——《设计模式之蝉》
引入外观角色之后,用户只需要直接与外观角色交互,用户与子系统之间的复杂关系由外观角色来实现,从而降低了系统的耦合度。
UML类图:
根据“单一职责原则”,在软件中将一个系统划分为若干个子系统有利于降低整个系统的复杂性,一个常见的设计目标是使子系统间的通信和相互依赖关系达到最小,而达到该目标的途径之一就是引入一个外观对象,它为子系统的访问提供了一个简单而单一的入口。
下面通过实例说明:
在计算机主机(Mainframe)中,只需要按下主机的开机按钮(on()),即可调用其他硬件设备和软件的启动方法,如内存(Memory)的自检(check())、CPU的运行(run())、硬盘(HardDisk)的读取(read())、操作系统(OS)的载入(load())等,如果某一过程发声错误则计算机启动失败。使用外观模式模拟该过程。
设计思路:计算机主机类MainFrame作为外观类,负责与客户端之间的交互,其中有on()方法,方法中调用Memory类、CPU类、HardDisk类、OS类中的启动方法。
public class MainFrame {
Memory memory=new Memory();
CPU cpu=new CPU();
HardDisk hardDisk=new HardDisk();
OS1 os1=new OS1();
public void on(){
memory.check();
cpu.run();
hardDisk.read();
os1.load();
}
}
class Memory{
public void check(){
System.out.println("checking the memory......");
}
}
class CPU{
public void run(){
System.out.println("start running......");
}
}
class HardDisk{
public void read(){
System.out.println("start reading the harddisk......");
}
}
class OS1{
public void load(){
System.out.println("start loading......");
}
}
优点:
•对客户屏蔽子系统组件,减少了客户处理的对象数目并使得子系统使用起来更加容易。通过引入外观模式,客户代码将变得很简单,与之关联的对象也很少。
•实现了子系统与客户之间的松耦合关系,这使得子系统的组件变化不会影响到调用它的客户类,只需要调整外观类即可。
•降低了大型软件系统中的编译依赖性,并简化了系统在不同平台之间的移植过程,因为编译一个子系统一般不需要编译所有其他的子系统。一个子系统的修改对其他子系统没有任何影响,而且子系统内部变化也不会影响到外观对象。
•只是提供了一个访问子系统的统一入口,并不影响用户直接使用子系统类。
缺点:
•不能很好地限制客户使用子系统类,如果对客户访问子系统类做太多的限制则减少了可变性和灵活性。
•在不引入抽象外观类的情况下,增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。
使用共享对象可有效地支持大量的细粒度的对象。——《设计模式之禅》
面向对象技术可以很好地解决一些灵活性或可扩展性问题,但在很多情况下需要在系统中增加类和对象的个数。当对象数量太多时,将导致运行代价过高,带来性能下降等问题。
享元模式正是为解决这一类问题而诞生的。享元模式通过共享技术实现相同或相似对象的重用。
下面通过实例说明:
使用享元设计模式设计一个围棋软件,在系统中只存在一个白棋对象和一个黑棋对象,但是它们可以在棋盘的不同位置显示多次。要求使用简单工厂模式和单例模式实现享元工厂类的设计。
设计思路:设计BlackChess和WhiteChess为具体实例类,继承自基类ShareChess。工厂类ChessFactory使用懒汉式单例,创建实例。
import java.util.*;
abstract class ShareChess {
public abstract String getColor();
public void show() {
System.out.println("棋子颜色:" + this.getColor());
}
}
class BlackChess extends ShareChess {
public String getColor() {
return "黑色";
}
}
class WhiteChess extends ShareChess {
public String getColor() {
return "白色";
}
}
class ChessFactory {
private static ChessFactory chess;
private static Hashtable hashtable;
private ChessFactory() {
hashtable = new Hashtable();
ShareChess blackChess,whiteChess;
blackChess = new BlackChess();
hashtable.put("black",blackChess);
whiteChess = new WhiteChess();
hashtable.put("white",whiteChess);
}
public static ChessFactory getChessInstance() {
if(chess==null){
chess=new ChessFactory();
return chess;
}
else return null;
}
public ShareChess getChess(String color) {
return (ShareChess)hashtable.get(color);
}
}
从代码中可见,享元模式的核心在于享元工厂类,享元工厂类的作用在于提供一个用于存储享元对象的享元池,用户需要对象时,首先从享元池中获取,如果享元池中不存在,则创建一个新的享元对象返回给用户,并在享元池中保存该新增对象。
上例中使用Hashtable是防止多线程中出现单例的破坏,我们都知道hashtable是线程安全的,其中多数方法都上了内置锁synchronized。
优点:
•享元模式的优点在于它可以极大减少内存中对象的数量,使得相同对象或相似对象在内存中只保存一份。
•享元模式的外部状态相对独立,而且不会影响其内部状态,从而使得享元对象可以在不同的环境中被共享。
缺点:
•享元模式使得系统更加复杂,需要分离出内部状态和外部状态,这使得程序的逻辑复杂化。
•为了使对象可以共享,享元模式需要将享元对象的状态外部化,而读取外部状态使得运行时间变长。
(其他设计模式日后再补...)