11 简单设计模式

设计模式(Design Pattern)是众多开发者根据他们的代码设计经验总结出来的模板。在编写代码时使用这些模板可以提高可重用性,可读性和可靠性。Java的设计模式一共有23种,这里简单总结一下自学视频中出现的几种设计模式。

1. 设计模式的原则

(1)可以扩展,难以修改;
(2)子类一定可以替换掉父类且不引发错误;
(3)利用接口扩展;
(4)利用多个接口降低耦合;
(5)实体间的连接尽可能少;
(6)多用聚合,少用继承。

2. 单例模式(Singleton)

单例模式的特点:
(1)单例类只能有一个实例;
(2)单例类必须自己创建实例;
(3)单例类必须给其它对象提供这个实例。
单例类的这些特点使得它往往用于硬件驱动程序:一个硬件只能有一个驱动程序,必须在开机后自启动,且能与应用层软件交互。这恰恰符合单例类的特点。

2.1 饿汉式

 class Singleton
{
    //单例类必须自己创建实例
    private Singleton(){}
    //单例类只能有一个实例
    private static final Singleton singleton = new Singleton();
    //单例类必须给其它对象提供这个实例
    public static Singleton getInstance()
    {
        return singleton;
    }
}

2.2 懒汉式

 class Singleton
{
    //单例类必须自己创建实例
    private Singleton(){}
    private static Singleton singleton;
    public synchronized static Singleton getInstance()
    {
        //单例类只能有一个实例
        if(singleton == null)
            singleton = new Singleton();
        //单例类必须给其它对象提供这个实例
        return singleton;
    }
}

2.3 联系与区别

从上面例子的注释可以看出,饿汉式和懒汉式都要满足单例模式的三个特点。区别在于如何确定单例类只能有一个实例。区别有三个方面:
(1)饿汉式是通过static和final关键字将单例类实例的引用设置成静态常量,多次调用getInstance()方法将返回属于类的(静态)固定指向的(常量)引用,即该单例类实例的引用。懒汉式同样将新实例的引用设置成静态的,但是通过判断来检查是否生成过实例,若生成过就不再生成;
(2)饿汉式的实例在单例类被加载的时候就会产生,而懒汉式的实例在调用getInstance()方法时才产生;
(3)饿汉式的内存消耗比懒汉式高,因为前者是加载类即实例化,后者是需要时才实例化;
(3)饿汉式是线程安全的。懒汉式是不安全的,因为判断语句和实例化语句存在时间差。这样虽然引用只有一个,却可能意外指向新产生的实例,不满足单例模式的第二个要求。

3. 工厂模式(Factory)

与单例模式相同的是,工厂模式也是创建实例的模板。不同的是,单例模式的目标是生成自己类的唯一且固定的实例,工厂模式的目标是运行时根据参数生成多个属于不同类的实例。

3.1 简单工厂模式(Simple Factory)

//产品的公共接口
interface Pet
{
    public void speak();
}
//具体产品要实现公共接口
class Dog implements Pet
{
    @Override
    public void speak()
    {
        System.out.println("WangWang");
    }
}

class Cat implements Pet
{
    @Override
    public void speak()
    {
        System.out.println("MiaoMiao");
    }
}

//工厂类
class Store
{
    public static Pet buy(String type)
    {
        if(type.equals("Dog"))
            return new Dog();
        else if(type.equals("Cat"))
            return new Cat();
        else
            throw new RuntimeException("We do not have it.");
    }
}

public class Test
{
    public static void main(String[] args)  
    {
        Store.buy("Dog").speak();
        Store.buy("Cat").speak();
    }
}
//运行结果:
//WangWang
//MiaoMiao
Store类就是工厂类。根据参数(宠物种类)生成对应宠物类的实例较为简单,难的是怎样返回这些实例。这里需要所有宠物类都实现同一个接口,以便将工厂类的返回值类型设置为该接口的类型。工厂类中的接口仅作为公共标签而存在,表示工厂类生产出的不同类的实例都属于一个系列,都可以视作该接口的具体实现而返回。至于接口内有没有方法,取决于这一个系列的类中彼此的相似程度,仅仅将接口作为公共返回值也可以。
为了提高扩展性,可以将上面例子中的工厂类Store中的字符串比对改成反射,这样如果增加了新宠物,无需修改工厂类,只要增加新的宠物类即可:
class Store
{
    public static Pet buy(String type) throws Exception
    {
        return (Pet) Class.forName(type).newInstance();
    }
}

3.2 工厂方法模式(Factory Method)

简单工厂模式的缺点在于无法体现多种产品间的体系结构,因为所有这些产品都是由一个工厂生产的。考虑许多种产品,它们之间有共性,但是同时它们也可以分成几组,每组之间有更深的共性,这时再用一个工厂来生产它们就不合适了。工厂方法模式就是要建立有共性的几个工厂类,每个工厂分别生产一组产品。这时要求这些工厂类具备公共接口,接口中有生产新产品的抽象方法。该抽象方法会在工厂类中重写。
import java.util.*;
import java.lang.reflect.*;

//两个工厂生产的四个产品的公共接口
interface Product{}
//衣服工厂生产的夹克
class Jacket implements Product{}
//衣服工厂生产的帽子
class Hat implements Product{}
//食品工厂生产的米
class Rice implements Product{}
//食品工厂生产的肉
class Meat implements Product{}

//两个工厂类的公共接口
interface Store
{
    public Product buy(String type);
}

//衣服工厂
class ClothesStore implements Store
{
    @Override
    public Product buy(String type)
    {
        if(type.equals("Jacket"))
            return new Jacket();
        else if(type.equals("Hat"))
            return new Hat();
        else
            throw new RuntimeException("Clothes store does not have it.");
    }
}

//食品工厂
class FoodStore implements Store
{
    @Override
    public Product buy(String type)
    {
        if(type.equals("Rice"))
            return new Rice();
        else if(type.equals("Meat"))
            return new Meat();
        else
            throw new RuntimeException("Food store does not have it.");
    }
}

public class Test
{
    public static void main(String[] args)
    {
        //创建衣服工厂
        Store clothesStore = new ClothesStore();
        //生产夹克和帽子
        System.out.println(clothesStore.buy("Jacket").getClass().getName());
        System.out.println(clothesStore.buy("Hat").getClass().getName());
        //创建食品工厂
        Store foodStore = new FoodStore();
        //生产米和肉
        System.out.println(foodStore.buy("Rice").getClass().getName());
        System.out.println(foodStore.buy("Meat").getClass().getName());
    }
}
//运行结果:
//Jacket
//Hat
//Rice
//Meat

3.3 联系与区别

(1)简单工厂模式只有一个工厂类,工厂方法模式具有多个共用接口的工厂类;
(2)简单工厂模式的工厂类在生产前需要一次实例化,工厂方法模式的多个工厂类可以视需求在不同的时间实例化;
(3)简单工厂模式只有一组产品,一级结构,工厂方法模式可以生产多组产品,两级结构。

4. 动态代理模式(Dynamic Proxy)

4.1 代理模式

当一个客户不想或不能直接引用一个类的对象时,代理对象可以在客户和它要引用的对象间起到中介的作用。代理模式的参与方有三个:
(1)客户接口:一组接口,委托类和代理类都实现这组接口。客户的作用是指示代理类的对象去操作委托类的对象;
(2)代理对象:代理类的对象,实现客户接口,也拥有委托类的对象的引用。因为代理对象和委托类的对象都实现客户接口,所以代理对象可以代替委托类的对象来满足客户要求。也因为代理对象拥有委托类的对象的引用,所以代理对象可以操作委托类的对象,比如调用委托类的方法。代理的作用是充当客户和委托类的中介,接收来自客户的要求,转给委托类的对象实际执行;
(3)委托类对象:委托类实现客户接口。委托类的对象接收代理对象转发来的客户要求,调用自己的方法来满足客户。

4.2 动态代理模式

根据上面的描述,代理要满足客户要求,必须实现客户接口,才能接收客户要求,必须拥有委托类的对象引用,才能将要求转给委托类的对象实际执行。这样的代理类非常不灵活,其实现的客户接口和拥有的委托类的对象是固定的。如果需要更换客户,或者用不同的委托类实际处理客户要求,就需要重写代理。将客户接口和委托类的对象作为构造参数传入,就可以重复利用同一个代理类关联不同的客户接口和委托类了。
动态代理模式(Dynamic Proxy Pattern)就是指代理通过构造参数获取客户接口和委托类的对象。具体体现在代理类需要三个构造参数来关联客户和委托类,一个是委托类的类加载器(通过反射获得),一个是客户接口(通过反射获得),一个是Handler对象(实现InvocationHandler接口的类的对象,实际处理代理转发来的客户要求,Handler对象通过构造参数获取委托类的对象的引用)。这样的设计可以提高代理类的可重用性。所谓动态就是代理在运行时才关联客户接口和委托类,这种关联可以在运行时改变。下面是一个简单的例子:
import java.lang.reflect.*;
public class Test
{
    public static void main(String[] args) throws Exception
    {
        Worker worker = new Worker();
        //java.lang.reflect.Proxy是代理类,Proxy.newProxyInstance()方法需要委托类的类加载器,委托类的一组接口和调用处理器三个参数来生成代理类的对象
        //newProxyInstance()是Proxy类的一个静态方法,返回一个Object类型的代理类的对象转换成接口赋值给客户接口
        Client client = (Client) Proxy.newProxyInstance(worker.getClass().getClassLoader(), worker.getClass().getInterfaces(), new ClientHandler(worker));
        //接口中的方法调用代理类的对象中的invoke()方法,该方法利用委托类的类对象提取到的方法对象和方法参数来运行委托类的方法,并将委托类的方法运行结果返回给代理类
        client.plan("jobNext");
        client.solve("jobNow");
    }
}
//使用代理的方法都被包含在一个接口中,成为客户接口
interface Client
{
    public void plan(String content);
    public void solve(String content);
}
//委托类:实现了客户接口中的方法,被代理类的对象委托来执行客户接口的方法
class Worker implements Client 
{
    public void plan(String content)
    {
        System.out.println("Planning of " + content + " done.");
    }
    public void solve(String content)
    {
        System.out.println("Solution of " + content + " done.");
    }
}
//java.lang.reflect.InvocationHandler接口是代理类的对象的调用处理程序实现的接口
//客户接口通知代理执行某方法,代理调用InvocationHandler接口中的处理程序,通常写在invoke()方法中
class ClientHandler implements InvocationHandler
{
    private Object proxyObject;
    //需要委托类的对象proxyObject作为构造参数,以便invoke()方法调用委托类的方法
    public ClientHandler(Object proxyObject)
    {
        this.proxyObject = proxyObject;
    }
    //invoke()方法处理代理类实例接到的所有方法调用,第一个参数是代理类实例,第二个参数是委托类被调用的方法的方法对象,第三个参数是委托类被调用的方法的参数
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
    {
        System.out.println("before working");
        Object rslt = method.invoke(proxyObject, args);
        System.out.println("after working");
        return rslt;
    }
}
//输出:
//before working
//Planning of jobNext done.
//after working
//before working
//Solution of jobNow done.
//after working
上面的例子中Client是客户接口。Client调用它的方法就是在向代理发出要求。Proxy类是代理,代理通过构造参数获得委托类的类加载器,客户接口,和实际处理客户要求的ClientHandler类的对象。ClientHandler类通过构造参数得到委托类Worker的一个对象,代理转发客户要求给ClientHandler类的对象,后者转发给Worker类的对象来实际执行。相比于静态代理模式的客户->代理->委托的处理顺序,动态代理模式使用客户->代理->Handler->委托的处理顺序。动态代理不仅可以动态关联客户和委托,还可以再增加一层灵活性,动态关联Handler(不同的方法来重写Handler的invoke()方法,可以生成不同的Handler),相当于给委托类做不同的包装。

4.3 用途

设计模式是根据大量实践总结出来的程序模板。不同的设计模式实质上是使用不同模板操作对象,以满足不同的使用场合。代理模式也是用来满足某些场合的:
(1)需要考虑不同的权限:不同用户对同一对象的操作权限不同,相当于不同权限的同一客户(同样的方法调用但权限不同)要求该对象给出不同反应。这时可以用代理来判断客户权限,然后用不同方法操作对象;
(2)需要以简代繁,防止阻塞:在网络上访问某个很大的资源(比如图片),限于网速需要等待,会导致该资源后面需要访问的资源都被阻塞。如果访问顺序无关紧要,可以使用代理来响应资源的访问,使得后面的访问不再等待。
(3)其它需要用不同方法包装同一个对象,以便对于不同访问给出不同反应的场合。此时代理的作用就是避免修改对象本身,而是修改中介。

5. 装饰模式(Decorator)

实现面向对象的扩展性,通常用类的继承。在子类中加上新的功能,就可以使用子类的实例来调用这些新功能。如果父类有可能扩展多个新功能,每当使用这些新功能的一个组合,就要重新写子类,这样是低效且不便的。更重要的问题是,所有子类都必须在运行前存在,而要求哪些新功能可能在运行时才确定。为了提高功能扩展的灵活性,且获得运行时扩展的能力,可以使用装饰模式。
//抽象功能
interface Function
{
    public void show();
}

// 抽象功能的A实现
class FunctionA implements Function
{
    @Override
    public void show()
    {
        System.out.println("A");
    }
}

//父类装饰器:不更改传入对象的功能
class Decorator implements Function 
{
    private Function function;
    public Decorator(Function function)
    {
        this.function = function;
    }

    @Override
    public void show()
    {
        function.show();
    }
}

//子类装饰器:获取父类装饰器接收的对象,并加上B实现
class DecoratorAddB extends Decorator implements Function
{
    public DecoratorAddB(Function function)
    {
        super(function);
    }

    private void showB()
    {
        System.out.println("B");
    }

    @Override
    public void show()
    {
        //对象本来的功能
        super.show();
        //装饰上去的功能
        this.showB();
    }
}

public class Test
{
    public static void main(String[] args)
    {
        Function function = new DecoratorAddB(new FunctionA());
        function.show();
    }
}
//运行结果:
//A
//B
父类和子类装饰器都需要实现抽象功能接口。父类装饰器用来运行被修饰的对象本来就有的功能,子类装饰器先运行父类装饰器中的功能,也就是被修饰的对象本来的功能,再运行增加的新功能。换一个子类装饰器,就可以使用不同的新功能。

6. 观察者模式(Observer)

观察者模式定义对象间的一对多的依赖关系,当一个对象(被观察者)的状态发生改变时, 所有依赖于它的对象(观察者)都得到通知并被自动更新。该模式需要java.util.Observable类(被观察者)和java.util.Observer接口(观察者)。
import java.util.*;

//被观察者
class Counter extends Observable
{
    private int count;
    public int getCount()
    {
        return count;
    }
    public void setCount(int count)
    {
        this.count = count;
    }
    public void counting()
    {
        for(int i = 0; i < 10; i++)
        {
            count = i;
            //标记为已改变
            this.setChanged();
            //通知所有观察者
            this.notifyObservers();
        }
    }
}

//观察者
class Alarm implements Observer
{
    //被观察者调用notifyObserver()方法,观察者即调用此方法
    @Override
    public void update(Observable o, Object arg)
    {
        //获取count值
        this.check(((Counter) o).getCount());
    }
    //根据count值作出反应
    private void check(int count)
    {
        if(count > 5 && count < 8)
            System.out.println(count);
    }
}

public class Test
{
    public static void main(String[] args)
    {
        Counter counter = new Counter();
        Alarm alarm = new Alarm();
        //添加alarm对象为观察者
        counter.addObserver(alarm);
        //开始计数
        counter.counting();
    }
}
//运行结果:
//6
//7
观察者模式可以隔离观察者和被观察者,使得两者可以在运行时改变(比如更换观察者)。缺点是较为浪费性能,而且被观察者和观察者的关系在代码中反映不明显,不易分析。

7. 总结

上面简单介绍了6种java设计模式。可以看出,单例模式、简单工厂模式、工厂方法模式是创建对象的模板,动态代理模式、装饰器模式、观察者模式是访问对象的模板。设计模式非常复杂,初学时应该总结每种模式的结构,用途和优缺点,以便日后使用。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值