设计模式入门

七大设计原则

单一职责原则

Single Responsibility Principle,SRP

定义

There should never be more than one reason for a class to change

作用

该原则提出对象不应该承担太多职责,如果一个对象承担了太多的职责,至少存在以下两个缺点:

  1. 一个职责的变化可能会削弱或者抑制这个类实现其他职责的能力
  2. 当客户端需要该对象的某一个职责时,不得不将其他不需要的职责全都包含进来,从而造成冗余代码或代码的浪费

单一职责原则的核心就是控制类的粒度大小、将对象解耦、提高其内聚性。如果遵循单一职责原则将有以下优点。

  1. 降低类的复杂度。一个类只负责一项职责,其逻辑肯定要比负责多项职责简单得多。 提高类的可读性。复杂性降低,自然其可读性会提高。
  2. 提高系统的可维护性。可读性提高,那自然更容易维护了。
  3. 变更引起的风险降低。变更是必然的,如果单一职责原则遵守得好,当修改一个功能时,可以显著降低对其他功能的影响。

实现

设计人员发现类的不同职责并将其分离,再封装到不同的类或模块中

例题

例1. 大学学生工作管理程序

大学学生工作主要包括学生生活辅导学生学业指导两个方面的工作,其中生活辅导主要包括班委建设出勤统计心理辅导费用催缴班级管理等工作,学业指导主要包括专业引导学习辅导科研指导学习总结等工作。
如果将这些工作交给一位老师负责显然不合理,正确的做法是生活辅导由辅导员负责,学业指导由学业导师负责,其类图如图所示。

在这里插入图片描述

里氏替换原则

Liskov Substitution Principle,LSP

定义

  1. Inheritance should ensure that any property proved about supertype objects also holds for subtype objects
  2. 子类可以扩展父类的功能,但不能改变父类原有的功能。
  3. 子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。

作用

里氏替换原则是实现开闭原则的重要方式之一。 它克服了继承中重写父类造成的可复用性变差的缺点。
它是动作正确性的保证。即类的扩展不会给已有的系统引入新的错误,降低了代码出错的可能性。

实现

取消原来的继承关系,重新设计它们之间的关系。

组合复用原则

Composite Reuse Principle,CRP

定义

在软件复用时,要尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现

作用

通常类的复用分为继承复用和合成复用两种,继承复用虽然有简单和易实现的优点,但它也存在以下缺点。

  1. 继承复用破坏了类的封装性。因为继承会将父类的实现细节暴露给子类,父类对子类是透明的,所以这种复用又称为“白箱”复用。
  2. 子类与父类的耦合度高。父类的实现的任何改变都会导致子类的实现发生变化,这不利于类的扩展与维护。
  3. 它限制了复用的灵活性。从父类继承而来的实现是静态的,在编译时已经定义,所以在运行时不可能发生变化。

采用组合或聚合复用时,可以将已有对象纳入新对象中,使之成为新对象的一部分,新对象可以调用已有对象的功能,它有以下优点。

  1. 它维持了类的封装性。因为成分对象的内部细节是新对象看不见的,所以这种复用又称为“黑箱”复用。
  2. 新旧类之间的耦合度低。这种复用所需的依赖较少,新对象存取成分对象的唯一方法是通过成分对象的接口。
  3. 复用的灵活性高。这种复用可以在运行时动态进行,新对象可以动态地引用与成分对象类型相同的对象。

实现

合成复用原则是通过将已有的对象纳入新对象中,作为新对象的成员对象来实现的,新对象可以调用已有对象的功能,从而达到复用。

例题

例1. 汽车分类管理程序

汽车按“动力源”划分可分为汽油汽车、电动汽车等;按“颜色”划分可分为白色汽车、黑色汽车和红色汽车等。如果同时考虑这两种分类,其组合就很多。图 1 所示是用继承:关系实现的汽车分类的类图。

在这里插入图片描述
可以看出用继承关系实现会产生很多子类,而且增加新的“动力源”或者增加新的“颜色”都要修改源代码,这违背了开闭原则,显然不可取。但如果改用组合关系实现就能很好地解决以上问题,其类图如图所示。
在这里插入图片描述

例2. 几维鸟不是鸟

鸟一般都会飞行,如燕子的飞行速度大概是每小时120千米。但是新西兰的几维鸟由于翅膀退化无法飞行。假如要设计一个实例,计算这两种鸟飞行300千米要花费的时间。显然,拿燕子来测试这段代码,结果正确,能计算出所需要的时间;但拿几维鸟来测试,结果会发生“除零异常”或是“无穷大”,明显不符合预期,其类图如图所示。

在这里插入图片描述

package principle;
public class LSPtest {
    public static void main(String[] args) {
        Bird bird1=new Swallow();
        Bird bird2=new BrownKiwi();
        bird1.setSpeed(120);
        bird2.setSpeed(120);
        System.out.println("如果飞行300公里:");
        try {
            System.out.println("燕子将飞行"+bird1.getFlyTime(300)+"小时.");
            System.out.println("几维鸟将飞行"+bird2.getFlyTime(300)+"小时。");
        } catch(Exception err) {
            System.out.println("发生错误了!");
        }
    }
}
//鸟类
class Bird {
    double flySpeed;
    public void setSpeed(double speed) {
        flySpeed=speed;
    }
    public double getFlyTime(double distance) {
        return(distance/flySpeed);
    }
}
//燕子类
class Swallow extends Bird{}
//几维鸟类
class BrownKiwi extends Bird {
    public void setSpeed(double speed) {
           flySpeed=0;
    }
}

程序运行错误的原因是:几维鸟类重写了鸟类的 setSpeed(double speed) 方法,这违背了里氏替换原则。正确的做法是:取消几维鸟原来的继承关系,定义鸟和几维鸟的更一般的父类,如动物类,它们都有奔跑的能力。几维鸟的飞行速度虽然为 0,但奔跑速度不为 0,可以计算出其奔跑 300 千米所要花费的时间。其类图如图所示。
在这里插入图片描述

例3. 正方形Square类和长方形Rectangle 类。

我们经常说继承是IS-A关系。 也就是说,如果两个类X、Y满足X IS-A Y关系,则X是Y的子类。
按照这个逻辑,正方形是一种长方形,那么正方形Square就应该继承长方形Rectangle。 但是这会导致问题:
1)对于Square来说,Rectangle 成员变量Height和Width其实只需要一个就可以。
2)对于Square继承自Rectangle 的SetWidth()和SetHeight()方法来说,实现起来很别扭。
因为,正方形没法单独使用SetWidth()和SetHeight()。 无论宽和高哪一个改变,Square都会同时修改另一个值。类Square其实是修改Rectangle的接口和实现,修改后的Square没法替换Rectangle。
在符合LSP的情况下,继承关系是能够帮助减少耦合的。

依赖倒置原则

Dependence Inversion Principle,DIP

定义

High level modules should not depend upon low level modules. Both should depend upon abstractions.
高层模块不应该依赖低层模块,它们都应该依赖抽象
Abstractions should not depend upon details.
抽象不应该依赖实现
Details should depend upon abstractions
实现应该依赖抽象

作用

降低类间的耦合性
提高系统的稳定性
减少并行开发引起的风险。
提高代码的可读性可维护性

实现

每个类尽量提供接口或抽象类,或者两者都具备。
变量的声明类型尽量是接口或者是抽象类。
任何类都不应该从具体类派生。
使用继承时尽量遵循里氏替换原则。

例题

例1.Windows的桌面主题设计

Windows的主题是桌面背景图片、窗口颜色和声音等元素的组合。用户可以根据自己的喜爱更换自己的桌面主题,也可以从网上下载新的主题。这些主题有共同的特点,可以为其定义一个抽象类(Abstract Subject),而每个具体的主题(Specific Subject)是其子类。用户窗体可以根据需要选择或者增加新的主题,而不需要修改原代码,所以它是满足开闭原则的,其类图如图所示。

在这里插入图片描述

迪米特法则

Law of Demeter,LOD

定义

1.Talk only to your immediate friends and not to strangers
2.如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。
3.当前对象本身、当前对象的成员对象、当前对象所创建的对象、当前对象的方法参数等,这些对象同当前对象存在关联、聚合或组合关系,可以直接访问这些对象的方法。

作用

优点:

  1. 降低了类之间的耦合度,提高了模块的相对独立性。
  2. 由于亲合度降低,从而提高了类的可复用率和系统的扩展性。

缺点:

  1. 过度使用迪米特法则会使系统产生大量的中介类,从而增加系统的复杂性,使模块之间的通信效率降低。

实现

从迪米特法则的定义和特点可知,它强调以下两点:

  1. 从依赖者的角度来说,只依赖应该依赖的对象。
  2. 从被依赖者的角度说,只暴露应该暴露的方法。

所以,在运用迪米特法则时要注意以下 6 点。

  1. 在类的划分上,应该创建弱耦合的类。类与类之间的耦合越弱,就越有利于实现可复用的目标。
  2. 在类的结构设计上,尽量降低类成员的访问权限。
  3. 在类的设计上,优先考虑将一个类设置成不变类。
  4. 在对其他类的引用上,将引用其他对象的次数降到最低。
  5. 不暴露类的属性成员,而应该提供相应的访问器(set和get方法)。
  6. 谨慎使用序列化(Serializable)功能。

例题

例1.明星与经纪人的关系实例

明星由于全身心投入艺术,所以许多日常事务由经纪人负责处理,如与粉丝的见面会,与媒体公司的业务洽淡等。这里的经纪人是明星的朋友,而粉丝和媒体公司是陌生人,所以适合使用迪米特法则,其类图如图所示。

在这里插入图片描述

//经纪人
class Agent {
    private Star myStar;
    private Fans myFans;
    private Company myCompany;
    public void setStar(Star myStar) {
        this.myStar=myStar;
    }
    public void setFans(Fans myFans) {
        this.myFans=myFans;
    }
    public void setCompany(Company myCompany) {
        this.myCompany=myCompany;
    }
    public void meeting() {
        System.out.println(myFans.getName()+"与明星"+myStar.getName()+"见面了。");
    }
    public void business() {
        System.out.println(myCompany.getName()+"与明星"+myStar.getName()+"洽淡业务。");
    }
}
//明星
class Star {
    private String name;
    Star(String name) {
        this.name=name;
    }
    public String getName() {
        return name;
    }
}
//粉丝
class Fans {
    private String name;
    Fans(String name) {
        this.name=name;
    }
    public String getName() {
        return name;
    }
}
//媒体公司
class Company {
    private String name;
    Company(String name) {
        this.name=name;
    }
    public String getName() {
        return name;
    }
}
public class LoDtest {
    public static void main(String[] args) {
        Agent agent=new Agent();
        agent.setStar(new Star("林心如"));
        agent.setFans(new Fans("粉丝韩丞"));
        agent.setCompany(new Company("中国传媒有限公司"));
        agent.meeting();
        agent.business();
    }
}
//就是把任务下放,自己的功能尽量单一,就是小类
//然后他说和间接类联系,就有点模糊
//所以我想,
//最好是只实现自己的职责,详细的职责交给其他人(职责划分)
//若是按我的想法,一个类就太大了
public class Teacher {
     //老师对学生发布命令,清一下女生
     public void commond(GroupLeader groupLeader){
             List listGirls = new ArrayList();
             //初始化女生
             for(int i=0;i<20;i++){
                     listGirls.add(new Girl());
             }
             //告诉体育委员开始执行清查任务
             groupLeader.countGirls(listGirls);
     }
}
public class GroupLeader {
     //清查女生数量
     public void countGirls(List<Girl> listGirls){
             System.out.println("女生数量是:"+listGirls.size());
     }
}
public class Girl {
}
public class Client {
     public static void main(String[] args) {
             Teacher teacher= new Teacher();
             //老师发布命令
             teacher.commond(new GroupLeader());
     }
}

例2.连锁商店销售系统

我们要获知每个已购买商品的价格,为此可以在Sales类的方法中调用:
salesList.getSalesLineItem().getCommodity().getPrice();
其顺序图如图所示。

在这里插入图片描述
这样的设计会使得Sales类和SalesList、SalesLineltem、 Commodity 都有访问耦合。
而在初始的类图设计中,Sales 类与SalesLineItem、Commodity 本来是没有关联的,这自然就不符合迪米特法则,会增加系统的复杂度和耦合性。
符合迪米特法则的更合理的设计代码和设计顺序图如下所示。
在这里插入图片描述

public class Sales {
    SaleList salesList = new SaleList();
    public double getCommodityPriceByID (long commodityID) {
        //调用SalesList的方法
        return salesList.getCommodityPrice (commodityID); 
    }
}
public class SalesList {
    HashMap<Integer,SalesLineItem> salesLineItemMap = new HashMap<Integer,SalesLineItem>() ;
    public double getCommodityPrice (long commodityID) {
        SalesLineItem item = salesLineItemMap.get(id) ;
        return item.getCommodityPrice() ;
    }
}
public class SaleLineItem {
    Commodity commodity;
    public double getCommodityPrice () {
        //调用Commodity的方法
        return commodity.getPrice() ;
    }
}
public class Commodity {
    double price;
    public double qetPrice() {
        return price;
    }
}

本来只有Sales类有getPrice方法,修改后其他类都有getPrice方法

例3.教师学生

public class Teacher {
     //老师对学生发布命令,清一下女生
     public void commond(GroupLeader groupLeader){
             //告诉体育委员开始执行清查任务
             groupLeader.countGirls();
     }
}
public class GroupLeader {
     private List<Girl> listGirls;
     //传递全班的女生进来
     public GroupLeader(List<Girl> _listGirls){
             this.listGirls = _listGirls;
     }
     //清查女生数量
     public void countGirls(){
             System.out.println("女生数量是:"+this.listGirls.size());
     }
}
public class Client {
     public static void main(String[] args) {
             //产生一个女生群体
             List<Girl> listGirls = new ArrayList<Girl>();
             //初始化女生
             for(int i=0;i<20;i++){
                     listGirls.add(new Girl());
             }
             Teacher teacher= new Teacher();
             //老师发布命令
             teacher.commond(new GroupLeader(listGirls));
     }
}

接口分离原则

Interface Segregation Principle,ISP

定义

  1. Clients should not be forced to depend on methods they do not use
  2. The dependency of one class to another one should depend on the smallest possible interface
  3. 要为各个类建立它们需要的专用接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。

作用

  1. 将臃肿庞大的接口分解为多个粒度小的接口,可以预防外来变更的扩散,提高系统的灵活性和可维护性。
  2. 接口隔离提高了系统的内聚性,减少了对外交互,降低了系统的耦合性。
  3. 如果接口的粒度大小定义合理,能够保证系统的稳定性;但是,如果定义过小,则会造成接口数量过多,使设计复杂化;如果定义太大,灵活性降低,无法提供定制服务,给整体项目带来无法预料的风险。
  4. 使用多个专门的接口还能够体现对象的层次,因为可以通过接口的继承,实现对总接口的定义。
  5. 能减少项目工程中的代码冗余。过大的大接口里面通常放置许多不用的方法,当实现这个接口的时候,被迫设计冗余的代码。

实现

  1. 接口尽量小,但是要有限度。一个接口只服务于一个子模块或业务逻辑。
  2. 为依赖接口的类定制服务。只提供调用者需要的方法,屏蔽不需要的方法。
  3. 了解环境,拒绝盲从。每个项目或产品都有选定的环境因素,环境不同,接口拆分的标准就不同深入了解业务逻辑。
  4. 提高内聚,减少对外交互。使接口用最少的方法去完成最多的事情。

例题

例1.学生成绩管理程序。

分析:学生成绩管理程序一般包含插入成绩、删除成绩、修改成绩、计算总分、计算均分、打印成绩信息、査询成绩信息等功能,如果将这些功能全部放到一个接口中显然不太合理,正确的做法是将它们分别放在输入模块、统计模块和打印模块等3个模块中,其类图如图所示。

在这里插入图片描述

interface InputModule {//输入模块接口
    void insert();
    void delete();
    void modify();
}
interface CountModule {//统计模块接口
    void countTotalScore();
    void countAverage();
}
interface PrintModule {//打印模块接口
    void printStuInfo();
    void queryStuInfo();
}
class StuScoreList implements InputModule,CountModule,PrintModule {//实现类
    private StuScoreList(){}
    public static InputModule getInputModule() {
        return (InputModule)new StuScoreList();
    }
    public static CountModule getCountModule() {
        return (CountModule)new StuScoreList();
    }
    public static PrintModule getPrintModule() {
        return (PrintModule)new StuScoreList();
    }
    public void insert() {
        System.out.println("输入模块的insert()方法被调用!");
    }
    public void delete() {
        System.out.println("输入模块的delete()方法被调用!");
    }
    public void modify() {
        System.out.println("输入模块的modify()方法被调用!");
    }
    public void countTotalScore() {
        System.out.println("统计模块的countTotalScore()方法被调用!");
    }
    public void countAverage() {
        System.out.println("统计模块的countAverage()方法被调用!");
    }
    public void printStuInfo() {
        System.out.println("打印模块的printStuInfo()方法被调用!");
    }
    public void queryStuInfo() {
        System.out.println("打印模块的queryStuInfo()方法被调用!");
    }
}
public class ISPtest {
    public static void main(String[] args) {
        InputModule input =StuScoreList.getInputModule();
        CountModule count =StuScoreList.getCountModule();
        PrintModule print =StuScoreList.getPrintModule();
        input.insert();
        count.countTotalScore();
        print.printStuInfo();
        //print.delete();
    }
}

例2.ATM取款机

在一个ATM应用中,为了实现非常灵活的用户界面(或者输出到屏幕上,或者通过语言输出,或者通过盲文书写板输出),设计师将所有交互功能集中起来,设计了一个抽象的UI接口。
但是,UI抽象接口的存在却导致各个事务对象出现了不必要的耦合:
DepositTransaction(存款)原本不需要依赖于除RequestDepositAmount()之外的任何接口;
WithdrawlTransaction(取款)原本不需要依赖于除RequestWithdrawl()和InformInsufficientFunds()之外的任何接口;
TransferTransaction (转账)原本不需要依赖于除RequestTransferAmount()之外的任何接口。

在这里插入图片描述
很明显统一负责各种交互的UI接口的存在给Transaction的各个子类带来了不必要的耦合。
但是UI抽象接口的存在合理性又是很明显的,它以简洁的方式实现了界面的灵活性,自身是高内聚的。

这时,就需要将一个统一的接口匹配为多个更独立的接口(如图14-4所示),这就是接口分离原则( Interface Segregation Principle, ISP ) [Martin1996a], 可以避免不必要的耦合,实现接口最小化。
在这里插入图片描述

开闭原则

Open Closed Principle,OCP

定义

Software entities should be open for extension,but closed for modification
当应用的需求改变时,在不修改软件实体的源代码或者二进制代码的前提下,可以扩展模块的功能,使其满足新的需求。

作用

开闭原则是面向对象程序设计的终极目标,它使软件实体拥有一定的适应性和灵活性的同时具备稳定性和延续性。具体来说,其作用如下:

  1. 对软件测试的影响。软件遵守开闭原则的话,软件测试时只需要对扩展的代码进行测试就可以了,因为原有的测试代码仍然能够正常运行。
  2. 可以提高代码的可复用性。粒度越小,被复用的可能性就越大;在面向对象的程序设计中,根据原子和抽象编程可以提高代码的可复用性。
  3. 可以提高软件的可维护性。遵守开闭原则的软件,其稳定性高和延续性强,从而易于扩展和维护。

实现

可以通过“抽象约束、封装变化”来实现开闭原则,即通过接口或者抽象类为软件实体定义一个相对稳定的抽象层,而将相同的可变因素封装在相同的具体实现类中。
因为抽象灵活性好,适应性广,只要抽象的合理,可以基本保持软件架构的稳定。而软件中易变的细节可以从抽象派生来的实现类来进行扩展,当软件需要发生变化时,只需要根据需求重新派生一个实现类来扩展就可以了。

例题

例1.使用多态实现OCP

例如,如图所示,Copy 类从ReadKeyboard类中读取字符,然后交给WritePrinter 输出。
假设现在需要增加个新的需求:有时需要使用WriteDisk类输出。那么按照下图进行的变更很明显是违反OCP的,因为它修改了原有代码。

在这里插入图片描述

正确的做法应该是使用多态机制,按照下图所示的方式进行变更。

在这里插入图片描述

例2. 使用DIP实现OCP

在连锁商店管理系统中,销售完成时会产生销售记录,并持久化保存。也就是说,业务模型Sales对象的endSales方法中需要向内存中Sales数据映射集SalesMapper对象里增加一条记录( insert方法),并且把这条记录更新到数据库中(save方法)。

通常的设计如下所示,Sales直接拥有SalesMapper对象的引用,并且直接调用其方法。这个设计中,Sales直接依赖于具体的实现类SalesMapper,耦合的方向不理想,强度就比较高。如果SalesMapper类发生变化,会直接影响Sales类。

public classs Sales {
    SalesMapper salesMapper;
    //如果有新的SalesMapper需要变为下面这句代码
    //NewSalesMapper salesMapper ;
    public void endSales () {
        salesMapper.insert(salesPO) ;
        salesMapper.save();
    }
    public void setSalesMapper (SalesMapper m) {
        salesMapper = m;
    }
}
public class Client {
    public static void main (String [] args) {
        //创建
        Sales s = new Sales () ;
        SalesMapper m = new SalesMapper() ;
        //如果有新的SalesMapper需要用下面的代码替换上面的代码
        //NewsalesMapper m = new NewSalesMapper () ;
        s.setSalesMapper(m);
        s.endSales () ;//调用
    }
}

设计不符合DIP,如果需要增加另一个类NewSalesMapper来实现insert和save等方法的时候,就需要修改Sales中与相关成员变量相关的代码,违反了OCP。

一个符合DIP的设计方案如下所示,让Sales持有一个SalesMapperService接口,然后用SalesMapper实现SalesMapperService接口。那么当变化发生的时候,Sales并不需要改变,也不需要重新编译。只需要修改Client类并重新编译即可。
当其需要增加另一个类SalesHashMapper的时候,就不需要修改Sales,只需要让SalesHashMapper实现SalesMapperService,就能替换原来的SalesMapper,符合0CP。

public interface SalesMapperService{
    ...
    public void insert(PO po) ;
    public void save() ;
}
public class SalesMapper implements SalesMapperService {
}
public classs Sales{
    SalesMapperService salesMapper;
    public void endSales() {
        salesMapper.insert (salesPO) ;
        salesMapper.save() ;
    }
    public void setSalesMapperService (SalesMapperService m) {
        salesMapper = m;
    }
}
pub1ic class Client {
    public static void main (String [] args) {
        //创建
        Sales s = new Sales () ;
        SalesMapperService m = new SalesMapper{) ;
        //如果有新的SalesMapper只需要用下面的代码替换.上面的代码即可
        //SalesMapperService m = new NewSalesMapper() ;
        s.setSalesMapper(m);
        s.endSales();//调用
    }
}

例3.顾客购物程序

商店类中有sell()方法,顾客类通过该方法购物以下代码定义了顾客类通过韶关网店ShaoguanShop购物:

class Customer {
    public void shopping(ShaoguanShop shop) {
        System.out.println(shop.sell());//购物
    }
}

但是,这种设计存在缺点,如果该顾客想从另外一家商店(如婺源网店WuyuanShop)购物,就要将该顾客的代码修改如下:

class Customer {
    public void shopping(WuyuanShop shop) {
        System.out.println(shop.sell());//购物
    }
}

顾客每更换一家商店,都要修改一次代码,这明显违背了开闭原则。存在以上缺点的原因是:顾客类设计时同具体的商店类绑定了,这违背了依赖倒置原则。解决方法是:定义“婺源网店”和“韶关网店”的共同接口 Shop,顾客类面向该接口编程,其代码修改如下:
在这里插入图片描述

class Customer {
    public void shopping(Shop shop) {
        System.out.println(shop.sell());//购物
    }
}

这样,不管顾客类 Customer 访问什么商店,或者增加新的商店,都不需要修改原有代码了,其类图如图所示。

interface Shop {//商店
    public String sell(); //卖
}
class ShaoguanShop implements Shop {//韶关网店
    public String sell() {
        return "韶关土特产:香菇、木耳……"; 
    } 
}
class WuyuanShop implements Shop { //婺源网店
    public String sell() {
        return "婺源土特产:绿茶、酒糟鱼……"; 
    }
} 
class Customer {//顾客
    public void shopping(Shop shop) {
        System.out.println(shop.sell()); //购物
    }
}
public class DIPtest {
    public static void main(String[] args) {
        Customer wang=new Customer();
        System.out.println("顾客购买以下商品:"); 
        wang.shopping(new ShaoguanShop()); 
        wang.shopping(new WuyuanShop());
    }
}

单例模式

定义

单例模式是一种对象创建型模式,使用单例模式,可以保证为一个类只生成唯一的实例对象。也就是说,在整个程序空间中,该类只存在一个实例对象。
其实,GoF对单例模式的定义是:保证一个类、只有一个实例存在,同时提供能对该实例加以访问的全局访问方法。

使用场景

在应用系统开发中,我们常常有以下需求:

  1. 在多个线程之间,比如servlet环境,共享同一个资源或者操作同一个对象
  2. 在整个程序空间使用全局变量,共享资源
  3. 大规模系统中,为了性能的考虑,需要节省对象的创建时间等等。

因为Singleton模式可以保证为一个类只生成唯一的实例对象,所以这些情况,Singleton模式就派上用场了。

实现

  1. 饿汉式。
  2. 懒汉式。
  3. 双重检查。

例题

例1.

public class Person {
	public static final Person person = new Person();
	private String name;
 
	public String getName() {
        return name;
	}
	public void setName(String name) {
        this.name = name;
	}
 
	//构造函数私有化
	private Person() {
	}
	//getPerson();
}
public class MainClass {
	public static void main(String[] args) {
        Person per1 = Person.getPerson();
        Person per2 = Person.getPerson();
        per1.setName("zhangsan");
        per2.setName("lisi");
        
        System.out.println(per1.getName());
        System.out.println(per2.getName());	
	}
}

1.饿汉式

//提供一个全局的静态方法
public static Person getPerson() {
    return person;
}

2.懒汉式

//提供一个全局的静态方法
public static Person getPerson() {
    if(person == null) {
        person = new Person();
    }
    return person;
}

2.1 解决懒汉问题

//提供一个全局的静态方法,使用同步方法
public static synchronized Person getPerson() {
    if(person == null) {
        person = new Person();
    }
    return person;
}

2.2 解决效率问题

//提供一个全局的静态方法
public static Person getPerson() {
    if(person == null) {
        synchronized (Person.class) {
            if(person == null) {
                person = new Person();
            }
        }
    }
    return person;
}

简单工厂模式

定义

简单工厂模式属于类的创建型模式,又叫做静态工厂方法模式。通过专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。

角色及职责

  1. 工厂(Creator)角色

简单工厂模式的核心,它负责实现创建所有实例的内部逻辑。工厂类可以被外界直接调用,创建所需的产品对象。

  1. 抽象(Product)角色

简单工厂模式所创建的所有对象的父类,它负责描述所有实例所共有的公共接口。

  1. 具体产品(Concrete Product)角色

简单工厂模式所创建的具体实例对象

优缺点

  1. 在这个模式中,工厂类是整个模式的关键所在。它包含必要的判断逻辑,能够根据外界给定的信息,决定究竟应该创建哪个具体类的对象。用户在使用时可以直接根据工厂类去创建所需的实例,而无需了解这些对象是如何创建以及如何组织的。有利于整个软件体系结构的优化。
  2. 不难发现,简单工厂模式的缺点也正体现在其工厂类上,由于工厂类集中了所有实例的创建逻辑,所以“高内聚”方面做的并不好。另外,当系统中的具体产品类不断增多时,可能会出现要求工厂类也要做相应的修改,扩展性并不很好。

例题

例1

基础代码

public interface Fruit {
	public void get();//采集
}
public class Apple implements Fruit{
	public void get(){//采集
        System.out.println("采集苹果");
	}
}
public class Banana implements Fruit{
	public void get(){//采集
        System.out.println("采集香蕉");
	}
}

初始代码

public class MainClass {
    public static void main(String[] args) throws 
     InstantiationException, IllegalAccessException, ClassNotFoundException {
        //实例化一个Apple,用到了多态
        Fruit apple = new Apple();
        Fruit banana = new Banana();
        apple.get();
        banana.get();
	}
}

修改代码

public class FruitFactory {
	/*
	 * get方法,获得所有产品对象
	 */
	public static Fruit getFruit(String type) throws 
     InstantiationException, IllegalAccessException {
        if(type.equalsIgnoreCase("apple")) {
            return Apple.class.newInstance();
        }else if(type.equalsIgnoreCase("banana")) {
            return Banana.class.newInstance();
        }else {
            System.out.println("找不到相应的实例化类");
            return null;
        }
	}
}
public class MainClass {
    public static void main(String[] args) throws 
        InstantiationException, IllegalAccessException {
        Fruit apple = FruitFactory.getFruit("Apple");
        Fruit banana = FruitFactory.getFruit("Banana");
        apple.get();
        banana.get();
	}
}

工厂方法模式

定义

工厂方法模式同样属于类的创建型模式又被称为多态工厂模式 。工厂方法模式的意义是定义一个创建产品对象的工厂接口,将实际创建工作推迟到子类当中。核心工厂类不再负责产品的创建,这样核心类成为一个抽象工厂角色,仅负责具体工厂子类必须实现的接口,这样进一步抽象化的好处是使得工厂方法模式可以使系统在不修改具体工厂角色的情况下引进新的产品。

角色及职责

  1. 抽象工厂(Creator)角色

工厂方法模式的核心,任何工厂类都必须实现这个接口。

  1. 具体工厂(Concrete Creator)角色

具体工厂类是抽象工厂的一个实现,负责实例化产品对象。

  1. 抽象(Product)角色

工厂方法模式所创建的所有对象的父类,它负责描述所有实例所共有的公共接口。

  1. 具体产品(Concrete Product)角色

工厂方法模式所创建的具体实例对象

和简单工厂比较

  1. 工厂方法模式与简单工厂模式在结构上的不同不是很明显。工厂方法类的核心是一个抽象工厂类,而简单工厂模式把核心放在一个具体类上。
  2. 工厂方法模式之所以有一个别名叫多态性工厂模式是因为具体工厂类都有共同的接口,或者有共同的抽象父类。
  3. 当系统扩展需要添加新的产品对象时,仅仅需要添加一个具体对象以及一个具体工厂对象,原有工厂对象不需要进行任何修改,也不需要修改客户端,很好的符合了“开放-封闭”原则。而简单工厂模式在添加新产品对象后不得不修改工厂方法,扩展性不好。
  4. 工厂方法模式退化后可以演变成简单工厂模式。

例题

例1.

public interface FruitFactory {
	public Fruit getFruit();
}
public class AppleFactory implements FruitFactory {
	public Fruit getFruit() {
        return new Apple();
	}
}
public class BananaFactory implements FruitFactory {
	public Fruit getFruit() {
        return new Banana();
	}
}
public class MainClass {
    public static void main(String[] args) {
        //获得AppleFactory
        FruitFactory ff = new AppleFactory();
        //通过AppleFactory来获得Apple实例对象
        Fruit apple = ff.getFruit();
        apple.get();
        
        //获得BananaFactory
        FruitFactory ff2 = new BananaFactory();
        Fruit banana = ff2.getFruit();
        banana.get();
        
        //获得PearFactory
        FruitFactory ff3 = new PearFactory();
        Fruit pear = ff3.getFruit();
        pear.get();
	}
}

抽象工厂模式

定义

抽象工厂模式是所有形态的工厂模式中最为抽象和最其一般性的。抽象工厂模式可以向客户端提供一个接口,使得客户端在不必指定产品的具体类型的情况下,能够创建多个产品族的产品对象。

角色及职责

  1. 抽象工厂(Creator)角色

抽象工厂模式的核心,包含对多个产品结构的声明,任何工厂类都必须实现这个接口。

  1. 具体工厂( Concrete Creator)角色

具体工厂类是抽象工厂的一个实现,负责实例化某个产品族中的产品对象。

  1. 抽象(Product)角色

抽象模式所创建的所有对象的父类,它负责描述所有实例所共有的公共接口。

  1. 具体产品(Concrete Product)角色

抽象模式所创建的具体实例对象

总结:抽象工厂中方法对应产品结构,具体工厂对应产品族。

例题

例1.

public class NorthApple extends Apple {
	public void get() {
        System.out.println("采集北方苹果");
	}
}
public class NorthBanana extends Banana {
	public void get() {
        System.out.println("采集北方香蕉");
	}
}
public class SouthApple extends Apple {
	public void get() {
        System.out.println("采集南方苹果");
	}
}
public class SouthBanana extends Banana {
	public void get() {
        System.out.println("采集南方香蕉");
	}
}
public interface FruitFactory {
	//实例化Apple
	public Fruit getApple();
	//实例化Banana
	public Fruit getBanana();
}
public class NorthFruitFactory implements FruitFactory {
	public Fruit getApple() {
        return new NorthApple();
	}
	public Fruit getBanana() {
        return new NorthBanana();
	}
}
public class SouthFruitFactory implements FruitFactory {
	public Fruit getApple() {
        return new SouthApple();
	}
	public Fruit getBanana() {
        return new SouthBanana();
	}
}
public class MainClass {
	public static void main(String[] args) {
        FruitFactory ff1 = new NorthFruitFactory();
        Fruit apple1 = ff1.getApple();
        apple1.get();
        
        Fruit banana1 = ff1.getBanana();
        banana1.get();
        
        FruitFactory ff2= new SouthFruitFactory();
        Fruit apple2 = ff2.getApple();
        apple2.get();
        
        Fruit banana2 = ff2.getBanana();
        banana2.get();
	}
}

策略模式

定义

Strategy模式也叫策略模式是行为模式之一,它对一系列的算法加以封装,为所有算法定义一个抽象的算法接口,并通过继承该抽象算法接口对所有的算法加以封装和实现,具体的算法选择交由客户端决定(策略)。Strategy模式主要用来平滑地处理算法的切换 。

结构

在这里插入图片描述

角色与职责

  1. Strategy

策略(算法)抽象。

  1. ConcreteStrategy

各种策略(算法)的具体实现。

  1. Context

策略的外部封装类,或者说策略的容器类。根据不同策略执行不同的行为。策略由外部环境决定。

作用

优点

  1. 策略模式提供了管理相关的算法族的办法。策略类的等级结构定义了一个算法或行为族。恰当使用继承可以把公共的代码移到父类里面,从而避免重复的代码
  2. 策略模式提供了可以替换继承关系的办法。继承可以处理多种算法或行为。如果不是用策略模式,那么使用算法或行为的环境类就可能会有一些子类,每一个子类提供一个不同的算法或行为。但是,这样一来算法或行为的使用者就和算法或行为本身混在一起。决定使用哪一种算法或采取哪一种行为的逻辑就和算法或行为的逻辑混合在一起,从而不可能再独立演化。继承使得动态改变算法或行为变得不可能。
  3. 使用策略模式可以避免使用多重条件转移语句。多重转移语句不易维护,它把采取哪一种算法或采取哪一种行为的逻辑与算法或行为的逻辑混合在一起,统统列在一个多重转移语句里面,比使用继承的办法还要原始和落后。

缺点

  1. 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。换言之,策略模式只适用于客户端知道所有的算法或行为的情况。
  2. 策略模式造成很多的策略类。有时候可以通过把依赖于环境的状态保存到客户端里面,而将策略类设计成可共享的,这样策略类实例可以被不同客户端使用。换言之,可以使用享元模式来减少对象的数量。

例题

例1.

public interface Strategy {
	public void encrypt();//加密
}
public class MD5Strategy implements Strategy{
	public void encrypt() {
        System.out.println("执行MD5加密");
	}
}
public class MDSStrategy implements Strategy{
	public void encrypt() {
        System.out.println("执行MDS加密");
	}
}
public class Context {
	private Strategy strategy;
 
	public Context(Strategy strategy) {
        this.strategy = strategy;
	}
	public void encrypt() {
        this.strategy.encrypt();
	}
}
public class MainClass {
	public static void main(String[] args) {
        Context context = new Context(new MDSStrategy());
        context.encrypt();
	}
}

迭代模式

定义

Iterator模式也叫迭代模式,是行为模式之一,它把对容器中包含的内部对象的访问委让给外部类,使用Iterator(遍历)按顺序进行遍历访问的设计模式。

使用场景

在应用Iterator模式之前,首先应该明白Iterator模式用来解决什么问题。或者说,如果不使用Iterator模式,会存在什么问题。

  1. 由容器自己实现顺序遍历。直接在容器类里直接添加顺序遍历方法
  2. 让调用者自己实现遍历。直接暴露数据细节给外部。

以上方法1与方法2都可以实现对遍历,这样有问题呢?

  1. 容器类承担了太多功能:一方面需要提供添加删除等本身应有的功能;一方面还需要提供遍历访问功能。
  2. 往往容器在实现遍历的过程中,需要保存遍历状态,当跟元素的添加删除等功能夹杂在一起,很容易引起混乱和程序运行错误等。

Iterator模式就是为了有效地处理按顺序进行遍历访问的一种设计模式,简单地说,Iterator模式提供一种有效的方法,可以屏蔽聚集对象集合的容器类的实现细节,而能对容器内包含的对象元素按顺序进行有效的遍历访问。
所以,Iterator模式的应用场景可以归纳为满足以下几个条件:

  1. 访问容器中包含的内部对象
  2. 按顺序访问

结构

在这里插入图片描述

角色和职责

  1. Iterator(迭代器接口)

该接口必须定义实现迭代功能的最小定义方法集比如提供hasNext()next()方法。

  1. ConcreteIterator(迭代器实现类)

迭代器接口Iterator的实现类。可以根据具体情况加以实现。

  1. Aggregate(容器接口)

定义基本功能以及提供类似Iterator iterator()的方法。

  1. concreteAggregate(容器实现类)

容器接口的实现类。必须实现Iterator iterator()方法。

优点

  1. 实现功能分离,简化容器接口。让容器只实现本身的基本功能,把迭代功能委让给外部类实现,符合类的设计原则。
  2. 隐藏容器的实现细节
  3. 为容器或其子容器提供了一个统一接口,一方面方便调用;另一方面使得调用者不必关注迭代器的实现细节。
  4. 可以为容器或其子容器实现不同的迭代方法或多个迭代方法。

例题

例1.

public class Book {
	private String ISBN;
	private String name;
	private double price;
	
	public Book(String isbn, String name, double price) {
        ISBN = isbn;
        this.name = name;
        this.price = price;
	}

	public String getISBN() {
        return ISBN;
	}
	public void setISBN(String isbn) {
        ISBN = isbn;
	}
	public String getName() {
        return name;
	}
	public void setName(String name) {
        this.name = name;
	}
	public double getPrice() {
        return price;
	}
	public void setPrice(double price) {
        this.price = price;
	}
	
	public void display() {
        System.out.println("ISBN=" + ISBN + ",name=" + name + ",price" + price);
	}	
}

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class BookList {
	private List<Book> bookList;
	private int index;
	private Iterator iterator;
	
	public BookList() {
        bookList = new ArrayList<Book>();
	}
	
	//添加书籍
	public void addBook(Book book) {
        bookList.add(book);
	}
	//删除书籍
	public void deleteBook(Book book) {
        int bookIndex = bookList.indexOf(book);
        bookList.remove(bookIndex);
	}
	public Iterator Iterator() {
        return new Itr();
	}
    private class Itr implements Iterator{
        public boolean hasNext() {
            if(index >= bookList.size()) {
                return false;
            }
            return true;
        }
        public Object next() {
            return bookList.get(index++);
        }
        public void remove() {	
        }
    }
}

import java.util.Iterator;
public class MainClss {
    public static void main(String[] args) {
        BookList bookList = new BookList();
        Book book1 = new Book("010203","Java编程思想",90);
        Book book2 = new Book("010204","Java从入门到精通",60);
        bookList.addBook(book1);
        bookList.addBook(book2);
        
        Iterator iter = bookList.Iterator();
        while(iter.hasNext()) {
            Book book = (Book) iter.next();
            book.display();
        }		
    }
}

观察者模式

定义

Observer模式是行为模式之一,它的作用是当一个对象的状态发生变化时,能够自动通知其他关联对象,自动刷新对象状态。
Observer模式提供给关联对象一种同步通信的手段,使某个对象与依赖它的其他对象之间保持状态同步

结构

在这里插入图片描述

角色和职责

  1. Subject(被观察者)

被观察的对象。当需要被观察的状态发生变化时,需要通知队列中所有观察者对象Subject需要维持(添加,删除,通知)一个观察者对象的队列列表

  1. ConcreteSubject

被观察者的具体实现。包含一些基本的属性状态及其他操作。

  1. Observer(观察者)

接口或抽象类。当Subject的状态发生变化时,Observer对象将通过一个callback函数得到通知。

  1. ConcreteObserver

观察者的具体实现。得到通知后将完成一些具体的业务逻辑处理。

典型应用

  1. 侦听事件驱动程序设计中的外部事件
  2. 侦听/监视某个对象的状态变化
  3. 发布者/订阅者(publisher/subscriber)模型中,当一个外部事件(新的产品,消息的出现等等)被触发时,通知邮件列表中的订阅者

例题

例1.

public class Person extends Observable {
	private String name;
	private String sex;
	private int age;

	public String getName() {
        return name;
	}
    public void setName(String name) {
        this.name = name;
        this.setChanged();
        this.notifyObservers();
	}
	public String getSex() {
        return sex;
	}
	public void setSex(String sex) {
        this.sex = sex;
        this.setChanged();
        this.notifyObservers();
	}
	public int getAge() {
        return age;
	}
	public void setAge(int age) {
        this.setChanged();
        this.notifyObservers();
        this.age = age;
	}
}
public class MyObServer implements Observer {
	public void update(Observable o, Object arg) {
        System.out.println("对象发生变化");
	}
}
public class MainClass {
	public static void main(String[] args) {
        Person person = new Person();
        //注册观察者
        person.addObserver(new MyObServer());
        person.addObserver(new MyObServer());
        System.out.println(person.countObservers());
        person.setName("lifengxing");
        person.setAge(23);
        person.setSex("男");
	}
}
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值