【设计模式】常见设计模式

  最近依然在应对面试,有人问到设计模式的题目,之前自己看过,但是都忘记了,现在整理一下。

1.单例模式

  
  有时,允许自由创建某个类的实例没有意义,反而可能会导致系统性能下降。例如:数据库引擎访问点、Hibernate的SessionFactory都只需要一个实例即可,此时可以使用单例模式。
  如果一个类始终只能创建一个实例,则称这个类为单例类,这种模式为单例模式。
  Spring中框架中可以直接在配置时通过制定scope=”singleton”实现单例模式。
  Java代码可以自己实现单例模式,如下:

class Singleton{
    //类变量缓存曾经创建的实例
    private static Singleton instance;
    //构造方法私有
    private Singleton(){}
    //静态方法,返回类的实例
    public static Singleton getInstance(){
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}

  思路:把构造方法设为私有,这样在类外就无法调用构造器生成对象的实例。然后在类中声明一个类变量用来缓存生成的实例。这样的话,还有个问题是无法生成这个对象的实例,因此需要写一个方法生成那个单例的对象实例。做法:声明一个静态方法,判断类变量对否为null,如果是null说明没有生成实例,就需要调用私有的构造方法生产一个实例;如果有了的话,就不需要如何操作。最后返回这个实例。
  

2.简单工厂模式

  
  开发过程中经常遇到A实例需要调用B实例。这个时候大多数做法是直接new一个B出来。这样的一个缺点就是代码耦合了,因为A中调用了B类的类名(硬编码耦合)。后果就是如果我们后来重构了,需要用C来替换B,这样的后果就是每个B都需要修改为C。工作量很大。
  解决方法:因为我们只是调用了B中的方法,不需要关心B的创建、实现过程,因此可以使用一个接口:IB,让B实现接口IB。这样A依赖的就不是具体的类了,而是具体的接口。然后创建一个工程类:IBFactory,该工厂负责产生IB的实例。A只需要调用IBFactory里面的方法就可以拿到IB的实例了。
  将来如果出现重构:C代替B的情况,只需要让C也实现IB接口,然后修改IBFactory里面代码,让工厂产生C实例就可以了。
  代码:
  IB接口。

public interface IB {
    public void show();
}

  B类

public class B implements IB {
    @Override
    public void show() {
        System.out.println("this is B!!");
    }
}

  C类

public class C implements IB {
    @Override
    public void show() {
        System.out.println("this is C!!");
    }
}

  IBFactory。工厂类,返回实例。

public class IBFactory {
    public IB getIB() {
        //如果是return new B();则返回B的实例
        //重构只需要修改这里。
        return new C();
    }
}

  A类。

public class A {
    private IB ib;
    public A(IB ib){
        this.ib = ib;
    }
    public void show(){
        ib.show();
    }

    public static void main(String[] args) {
        IBFactory ibFactory = new IBFactory();
        A a = new A(ibFactory.getIB());
        a.show();
    }
}

3.工厂方法和抽象工厂

  
  (1)工厂方法:
  在上面2中,系统通过工厂方法生产了对象的实例,在工厂类中决定生产那个对象的实例。但是这样也不太完美。修改生产的产品是,需要去工厂里面直接修改代码,这样不太好。希望在调用工厂的时候(即在A中),直接可以判断需要生产什么产品。换言之,重构时,只需要修改A中代码就可以了。
  解决办法,可以设计一个工厂的接口,程序为不同的产品设计不同的工厂。
  代码:
  Factory类。生产不同工厂的工厂类。

public interface Factory {
    IB getIB();
}

  BFactory.java。生产B产品的工厂。

public class BFactory implements Factory {
    @Override
    public IB getIB() {
        return new B();
    }
}

  CFactory.java。生产C产品的工厂。

public class CFactory implements Factory {
    @Override
    public IB getIB() {
        return new C();
    }
}

  IB.java,B.java,C.java与前面简单工厂类一样,不在赘述。
  A.java代码。

public class A {
    private IB ib;
    public A(IB ib){
        this.ib = ib;
    }
    public void show(){
        ib.show();
    }

    public static void main(String[] args) {
        //使用CFactory子类创建Factory
        Factory factory =  new CFactory();
        A a = new A(factory.getIB());
        a.show();
    }
}

  (2)抽象工厂
  对于上面的工厂方法,依然存在一种耦合:客户端代码与不同的工厂类耦合。解决这个办法可以再增加一个工厂类,这个类来制造不同的工厂:
  代码:
  添加类FactoryFactory.java

public class FactoryFactory {
    public static Factory getFactory(String type){
        if(type.equalsIgnoreCase("b"))
            return new BFactory();
        else
            return new CFactory();
    }
}

  A.java修改代码:

public class A {
    private IB ib;
    public A(IB ib){
        this.ib = ib;
    }
    public void show(){
        ib.show();
    }

    public static void main(String[] args) {
        //通过传参来确定生产的工厂
        Factory factory =  FactoryFactory.getFactory("B");
        A a = new A(factory.getIB());
        a.show();
    }
}

  
  “抽象工厂”与“简单工厂”区别:简单工厂直接生产对被调用对象;抽象工厂生产工厂对象。
  Spring框架的IoC容器可以认为是抽象工厂。可以管理Bean实例,也可以管理工厂实例。
  

4.代理模式

  当客户端代码需要调用某个对象的时候,客户端实际上不关心是否准确得到了这个对象,只要一个能提供该功能的对象即可,此时可以返回对象的代理。
  这种设计模式下,系统会为某个对象生成一个代理,由这个代理控制源对象的引用。这种情况下,客户端代码仅仅持有一个被代理对象的接口,变为面向接口编程。
  其实这里可以看看动态代理、静态代理的知识。
  代码:
  BigImage.java。模拟大的图片。

public class BigImage implements Image {
    public BigImage(){
        try {
            //暂停3秒,模拟系统开销
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    @Override
    public void show() {
        System.out.println("image show...");
    }
}

  ProxyImage.java,图片的代理类。

public class ProxyImage implements Image {

    private Image image;

    public ProxyImage(Image image){
        this.image = image;
    }

    @Override
    public void show() {
        if(image == null){
            image = new BigImage();
        }
        image.show();
    }
}

  测试类。

public class ProxyTest {
    public static void main(String[] args) {
        long startTime = System.currentTimeMillis();
        Image image = new ProxyImage(null);
        System.out.println("加载图片耗时:"+(System.currentTimeMillis()-startTime));
        //调用show方法时,才会真正执行加载
        long startTime2 = System.currentTimeMillis();
        image.show();
        System.out.println("加载图片真正耗时:"+(System.currentTimeMillis()-startTime2));
    }
}

  结果:
  【设计模式】常见设计模式_第1张图片

  Hibernate框架使用了代理模式,比如它的延迟加载。加入A、B实体存在关联关系,在加载A实体时,B应该被加载。采取延迟加载,系统会生产一个B的代理,只有A真正需要访问B中变量的时候,才会真正加载B实体。从而节约开销。

5.命令模式

  很多时候,我们需要一个方法完成一个功能,而且,这个功能大多数步骤我们已知并确定,但是有少量具体的步骤,需要在执行这个方法的时候,才能确定。这个时候可以使用命令模式。比如:我们有一个方法遍历数组的每一个元素,但是对元素的具体操作(迭代输出、累加)需要在调用该方法的时候才能确定,则需要在调用该方法时指定具体的处理行为。
  代码:
  ProcessArray.java,里面有一个each方法,用于处理数组。具体操作不知道,所以需要传入一个Command参数。

public class ProcessArray {
    public void each(int[] target, Command cmd) {
        cmd.process(target);
    }
}

  Command.java,里面的process方法定义了对于数组的处理行为。这是一个接口

public interface Command {
    void process(int[] target);
}

  CommandTest.java。测试命令方法。

public class CommandTest {
    public static void main(String[] args) {
        ProcessArray processArray = new ProcessArray();
        int[] target = {5, 2, 8, 4};
        processArray.each(target, new Command() {
            @Override
            public void process(int[] target) {
                for (int temp : target) {
                    System.out.println("迭代输出:" + temp);
                }
            }
        });
    }
}

  如果需要对数组进行其他的操作,只需要修改传入的匿名类的实例。不同的Command实例封装了不同的操作。
  

6.策略模式

  
  如果现在有这样一个需求,有一件商品,不同的用户(普通会员、VIP)所享受到的折扣不一样。需要针对不同的打折需求计算不同的价格。以往的做法就是switch()语句解决。但是一个弊端就是,如果某天我们增加了一个新的打折情况,则需要在switch中添加新的case,并添加新的语句,然后针对新的打折。现在使用策略模式,我们只需要新建一个类,实现统一的接口,描述这种打折的具体操作就可以了,不需要修改之前的代码。
  代码:
  首先我们写打折接口。DiscountStrategy.java。

public interface DiscountStrategy {
    double getDiscount(double originPrice);
}

  其次两个打折具体类,实现这个接口。
  普通用户打折。CommonDiscount .java。

public class CommonDiscount implements DiscountStrategy {
    @Override
    public double getDiscount(double originPrice) {
        System.out.println("普通用户打折...");
        return originPrice * 0.7;
    }
}

  VIP用户的打折。VIPDiscount .java。

public class VIPDiscount implements DiscountStrategy {
    @Override
    public double getDiscount(double originPrice) {
        System.out.println("VIP 打五折...");
        return originPrice * 0.5;
    }
}

  选择策略算法、返回价格的类。DiscountContext.java。

public class DiscountContext {
    private DiscountStrategy strategy;
    public DiscountContext(DiscountStrategy strategy){
        this.strategy = strategy;
    }

    public double getDiscount(double price){
        return strategy.getDiscount(price);
    }

    //改变策略
    public void changeStrategy(DiscountStrategy strategy){
        this.strategy = strategy;
    }
}

  测试

public class Test {
    public static void main(String[] args) {
        //null代表普通用户
        DiscountContext discountContext = new DiscountContext(new CommonDiscount());
        double price = 998.99;
        System.out.println("普通用户价格:" + discountContext.getDiscount(price));
        discountContext.changeStrategy(new VIPDiscount());
        System.out.println("VIP用户价格:" + discountContext.getDiscount(price));
    }
}

  之后的代码修改中,如果新增加一种打折方式,只需要写一个实现了打折接口的类,然后在测试类中添加

discountContext.changeStrategy(new /*打折类*/); 

  就可以了。
  
  Hibernate中数据库方言Dialect就是使用的这种模式。

7.门面模式

  
  很多情况下,我们进行一个操作的步骤是固定的。比如餐馆点餐吃饭,首先会有点餐,其次厨师做饭,最后服务员上菜…这些步骤都是一定的了。但是每次这样一个过程都需要调用三个类(订餐、做饭、上菜),比较麻烦,我们可以写一个类(门面),类中一次调用这三个类。在以后顾客点餐的时候,直接调用这个门面。
 代码:
 PayMent.java。订餐。

public class Payment {
    public String pay(String food) {
        System.out.println("点餐:" + food + "一份!");
        return food;
    }
}

  Cook.java。厨师类。

public class Cook {
    public String cook(String food) {
        System.out.println("厨师正在烹调:" + food);
        return food;
    }
}

  Serve.java。服务类。

public class Serve {
    public void serve(String food) {
        System.out.println("服务员上菜:" + food);
    }
}

  Facade.java。门面类。

public class Facade {
    Payment payment;
    Cook cook;
    Serve serve;
    public Facade() {
        this.payment = new Payment();
        this.cook = new Cook();
        this.serve = new Serve();
    }
    public void serveFood(String food){
        payment.pay(food);
        cook.cook(food);
        serve.serve(food);
    }
}

  测试

public class Test {
    public static void main(String[] args) {
        Facade facade = new Facade();
        facade.serveFood("牛肉面");
    }
}

  我们在门面里面封装了要执行的步骤,在程序中可以直接调用门面完成一系列操作。
  Hibernate框架中,hibernateTemplate就是用了这种模式。例如save操作,封装了SessionFactory、session等门面。
  

8.桥接模式

  
  开发中,遇到两个维度的结构模式,仅仅使用继承无法实现。比如面条,可能有材料维度(牛肉、羊肉、猪肉),也有口味维度(清淡、微辣)等等。这个时候可以这样解决。
  设置一个接口,专门记录口味的。不同的口味,对应一个实现类。
  设置一个抽象类,该类有一个变量记录面条的口味。然后对于具体的材料(猪肉、牛肉等)继承这个抽象类。
  这样对于添加口味维度或者材料维度,都只需要添加一个类就可以。
  代码:
  口味维度的两个类。

public class PepperyStyle implements Peppery {
    @Override
    public String style() {
        return "辣口味";
    }
}
public class PlainStyle implements Peppery {
    @Override
    public String style() {
        return "清淡口味";
    }
}

  面条抽象类。

public abstract class AbstractNoodle {
    Peppery style;
    public AbstractNoodle(Peppery style){
        this.style = style;
    }
    public abstract void eat();
}

  牛肉面以及猪肉面类。

public class BeefNoodle extends AbstractNoodle {

    public BeefNoodle(Peppery style) {
        super(style);
    }

    @Override
    public void eat() {
        System.out.println("牛肉面,口味:" + super.style.style());
    }
}
public class ProkyNoodle extends AbstractNoodle {

    public ProkyNoodle(Peppery style) {
        super(style);
    }

    @Override
    public void eat() {
        System.out.println("猪肉面,口味:" + super.style.style());
    }
}

  测试类。

public class Test {
    public static void main(String[] args) {
        AbstractNoodle noodle1 = new BeefNoodle(new PepperyStyle());
        noodle1.eat();
        AbstractNoodle noodle2 = new ProkyNoodle(new PlainStyle());
        noodle2.eat();
    }
}

9.观察者模式

  比较常见的一种模式。
  定义了对象间一对多的依赖关系:一个或者多个观察者对象观察一个主题对象。当主题对象发生变化时,系统通知所有的观察此对象的观察者。
  一般包括四个角色:
  被观察者的基类对象:持有多个观察者的引用。
  观察者接口:所有观察者必须实现的一个接口。
  被观察者实现类:继承被观察者的基类对象。
  观察者实现类:实现观察者接口。

你可能感兴趣的:(java,设计模式,设计模式)