美文网首页Java基础
单例模式、工厂方法模式、代理模式、装饰者模式、观察者模式

单例模式、工厂方法模式、代理模式、装饰者模式、观察者模式

作者: Merbng | 来源:发表于2021-04-27 16:00 被阅读0次

    设计模式六大原则

    • 单一职责
    • 开放封闭
    • 里氏替换原则
    • 依赖倒置
    • 迪米特原则
    • 接口隔离原则

    单例设计模式

    • 饿汉式
    public class Singleton{
        private static Singleton instance = new Singleton();
     
        private Singleton(){
     
        }
        public static Singleton getInstance(){
            return instance;
        }
    
    }
    

    这种方式在类创建时就完成了初始化,所以类加载慢,但是获取对象的速度快。
    这种方式基于类加载机制,避免了多线程的同步问题。
    在类加载的时候就完成初始化,没有达到懒加载的效果。如果从始至终未使用过这个实例,则会造成内存浪费

    • 懒汉式
    public Singleton{
    
        private static Singleton instance;
    
        private Singleton(){
    
        }
        public static Singleton getInstance(){
            if(instance==null){
                instance=new Singleton();
            }
            return instance;
        }
    
    }
    

    懒汉模式声明了一个静态对象,在用户第一次调用时初始化,这虽然节约了资源,但第一次加载时需要实例化,反应稍慢一些,
    而且在多线程时不能正常工作

    • 懒汉式(线程安全)
    public class Singleton {
        private static Singleton instance;
    
        private Singleton(){}
    
        public static synchronized Singleton getInstance(){
            if(instance==null){
                instance=new Singleton();
            }
            return instance;
        }
    
    }
    

    这种写法能够在多线程中很好地工作,但是每次调用getInstance方法时都需要进行同步,这会造成不必要的同步开销
    而且大部分时候我们是用不到同步的,所以不建议用这种模式。

    • 双重校验模式(DCL)
    public class Singleton{
        private volatile static Singleton instance;
        private Singleton(){}
        public static Singleton getInstance(){
            if(instance==null){
            synchronized(Singleton.class){
                if(instance==null){
                    instance=new Singleton();
                }
            }
            }
            return instance;
        }
    
    }
    

    这种写法在getInstance方法中对Singleton进行了两次判空,第一次是为了不必要的同步,第二次是在Singleton等于null的情况下才创建实例。
    在这里volatile会或多或少的影响性能,但考虑到程序的正确性,牺牲这点性能还是值得的。DCL的优点是资源利用率高。第一次执行genInstance时单例对象才被实例化,效率高。其缺点是第一次加载时反应稍慢一些,在高并发环境下也有一定的缺陷。
    DCL虽然在一定程度上解决了资源的消耗和多余的同步、线程安全等问题,但其还是在某些情况会出现失效的问题,这里建议用静态内部类单例模式来替代DCL。

    • 静态内部类单例模式
    public class Singleton{
    
        private Singleton(){}
        public static Singleton getInstance(){
            return SingletonHolder.instance;
        }
        private static class SingletonHolder{
            private static final Singleton instance =new Singleton();
        }
    
    }
    

    第一次加载Singleton类时并不会初始化instance,只有第一次调用getInstance方法时虚拟机加载SingletonHolder并初始化instance
    这样不仅能确保线程安全,也能保证Singleton类的唯一性。所以,推荐使用静态内部类单例模式。

    • 枚举单例
    public enum Singleton{
        INSTANCE;
        public void doSomeThing(){}
    }
    

    默认枚举实例创建是线程安全的,并且在任何情况下都是单例。
    上面讲的几种单例模式的实现中,有一种情况下其会重新创建对象,那就是反序列化:将一个单例实例对象写到磁盘再读回来,从而获得了一个实例。
    反序列化操作提供了readResolve方法,这个方法可以让开发人员控制对象的反序列化。
    在上面几个方法事例中,如果要杜绝单例对象被反序列化时重新生成对象,就必须加入如下方法:

    private Object readResolve() throws ObjectStreamException{
        return instance;
    }
    

    枚举单例的优点就是简单,其可读性并不是很高。

    工厂方法模式

    public abstract class Computer{
        public abstract void start();
    }
    public abstract class ComputerFactory{
        public abstract <T extends Computer> T createComputer(Class<T> clz);
    }
    
    public class GDComputerFactor extends ComputerFactory{
        @Override
        public <T extends Computer> T createComputer(Class<T> clz){
            Computer computer =null;
            String classname= clz.getName();
            try{
                computer =(Computer)Class.forName(classname).newInstance();
                
            }catch(Exception e){
                e.printStackTrace();
            }
            return (T)computer;
        }
    }
    //客户端调用
    public class Client{
    
        public start void main(String[]args){
            ComputerFactory computerFactory =new GDComputerFactor();
            LenovoComputer mlenovoComputer = computerFactory.createComputer(LenovoComputer.class);
            mlenovoComputer.start();
        }
    
    }
    

    代理模式

    • 静态代理
    //抽象主题类
    public interface IShop{
        void buy();
    }
    //真实主题类
    public class Merbng impelements IShop{
        @Override
        public void buy(){
            System.out.println("购买");
        }
    }
    //代理类
    public class Purchasing impelements IShop{
        private IShop mShop;
        public Purchasing(IShop shop){
            mShop=shop
        }
            @Override
            public void buy(){
                mShop.buy();
            }
    }
    //客户端类
    public class Client{
        public static void main(String[] args){
            IShop merbng =new Merbng();
            IShop purchasing =new Purchasing(merbng);
            purchasing.buy();
        }
    }
    
    • 动态代理
    public class DynamicPurchasing impelements InvocationHandler{
        private Object obj;
        public DynamicPurchasing(Object obj){
            this.obj =obj;
        }
        @Override
        public Object invoke(Object proxy,Method method,Object[] args)throws Throwable{
            Object result = method.invoke(obj,args);
            if(method.getName().equals("buy")){
                System.out.println("在买买买");
            }
            return result;
        }
    }
    

    在动态代理类中我们声明一个Object的引用,该引用指向被代理类,我们调用被代理类的具体方法在invoke()方法中执行。
    接下来我们修改客户端类代码:

    public class Client{
        public start void main(String[]args){
            //创建Merbng
            IShop merbng =new Merbng();
            //创建动态代理
            DynamicPurchasing mDynamicPurchasing =new DynamicPurchasing(merbng);
            //创建Merbng的ClassLoader
            ClassLoader loader =merbng.getClass().getClassLoader();
            //动态创建代理类
            IShop purchasing=(IShop)Proxy.newProxyInstance(loader,new Class[]{IShop.class},mDynamicPurchasing);
            purchasing.buy();
        }
    }
    

    调用Proxy.newProxyInstance()来生成动态代理类,调用purchasing的buy方法会调用DynamicPurchasing的invoke方法。

    代理模式的优点主要有以下几点:

    • 真实主题类就是实现实际的业务逻辑,不用关心其他非本职的工作。
    • 真实主题类随时会发生变化;但是因为它实现了公共接口,所以代理类可以不做任何修改就能够使用。

    装饰者模式

    //饮料抽象类
    public abstract class Beverage{
        String description = "Unknown Beverage";
        public String getDescription(){
            return description;
        }
        //cost方法是用来返回饮料的价钱(需在具体类中自己实现)
        public abstract BigDecimal cost();
    }
    //深焙咖啡类(一种具体的饮料)
    public class DarkRoast extends Beverage{
        public DarkRoast(){
            description="DarkRoast";
        }
        //实现cost方法,用来返回DarkRoast的价格
        @Override
        public BigDecimal cost(){
            return new BigDecimal("3.00");
        }
    }
    //低咖啡因咖啡类(一种具体的饮料)
    public class Decaf extends Beverage{
        public Decaf(){
            description="Decaf"
        }
    
        //实现cost方法,用来返回Decaf的价格
        @Override
        public BigDecimal cost(){
            return new BigDecimal("4.00");
        }
    }
    
    //浓缩咖啡类(一种具体的饮料)
    public class Espresso extends Beverage{
        public Espresso(){
            description="Espresso"
        }
    
        //实现cost方法,用来返回Espresso的价格
        @Override
        public BigDecimal cost(){
            return new BigDecimal("2.00");
        }
    }
    //调料装饰者抽象类(继承自饮料抽象类)
    public abstract class CondimentDecorator extends Beverage{
        //所有的饮料装饰者类都必须重新实现getDescription()方法。这样才能够用递归的方式来得到所选饮料的整体描述
        public abstract String getDescription();
    }
    
    //摩卡调料类(继承自CondimentDecorator)
    public class Mocha extends CondimentDecorator{
        //用一个实例变量记录饮料,也就是被装饰者
        Beverage beverage;
        public Mocha (Beverage beverage){
            this.beverage=beverage;
        }
        //在原来饮料的基础上添加上Mocha的描述(原来的饮料加入Mocha调料,被Mocha调料装饰)
        @Override
        public String getDescription(){
            return beverage.getDescription() + ", Mocha";
        }
        //在原来饮料的基础上加上Mocha的价格
        @Override
        public BigDecimal cost(){
            return new BigDecimal("0.2").add(beverage.cost());
        }
    }
    //豆浆调料类(继承自CondimentDecorator)
    public class Soy extends CondimentDecorator{
        Beverage beverage;
        public Soy(Beverage beverage){
            this.beverage=beverage;
        }
        @Override
        public String getDescription(){
            reutrn beverage.getDescription()+",Soy";
        }
        @Override
        public BigDecimal cost(){
            return new BigDecimal("0.3").add(beverage.cost());
        }
    }
    //奶泡调料类(继承自CondimentDecorator)
    public  class Whip extends CondimentDecorator{
        Beverage beverage;
        public Whip(Beverage beverage){
            this.beverage=beverage;
        }
        @Override
        public String getDescription(){
            return beverage.getDescription()+"Whip";
        }
        @Override
        public BigDecimal cost(){
            return new BigDecimal("0.4").add(beverage.cost());
        }
    }
    
    //咖啡馆(供应咖啡)
    public class StarbuzzCoffee{
        public start void main(String[] args){
        //订一杯 Espresso(2.00)不需要调料,打印出描述和价钱
            Beverage beverage=new Espresso();
            System.out.println("Description:"+beverage.getDescription()+" $"+beverage.cost());
            //制造出一个DarkRoast(3.00)对象,用Mocha(0.2)装饰它,用第二个Mocha(0.2)装饰它,用Whip(0.4)装饰它,打印出描述和价格
            Beverage beverage2=new DarkRoast();
            beverage2=new Mocha(beverage2);
            beverage2=new Mocha(beverage2);
            beverage2=new Whip(beverage2);
            System.out.println("Description:"+ beverage2.getDescription()+"$"+beverage2.cost());
            //再来一杯调料为豆浆(Soy 0.3)、摩卡(Mocha 0.2)、奶泡(Whip 0.4)的 Decaf(低咖啡因咖啡 4.0)。打印他的描述和价格
            Beverage beverage3 =new Decaf();
            beverage3=new Soy(beverage3);
            beverage3=new Mocha(beverage3);
            beverage3=new Whip(beverage3);
            System.out.println("Description:"+beverage3.getDescription()+"$"+beverage3.cost());
            
        }
    }
    运行结果:
    Description: Espresso $2.00
    Description: DarkRoast,Mocha,Mocha,Whip $3.9
    Description: Decaf,Soy,Mocha,Whip $4.90
    
    
    

    从以上,我们可以知道,当我们使用继承,导致子类膨胀,我们不想增加很多子类的情况下,将具体功能职责划分,同时继承装饰者超类,动态给一个对象添加一些额外的职责便实现了我们的装饰者模式。

    • 优点:
      装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
    • 缺点:
      多层修饰比较复杂

    观察者模式

    关于观察者模式这种发布-订阅的形式,我们可以拿微信公众号来举例。
    假设微信用户就是观察者,微信公众号是被观察者,有多个微信用户关注了同一个公众号,
    当这个公众号更新时就会通知这些订阅的微信用户,代码实现如下:

    //抽象观察者  里面定义了 一个更新的方法
    public interface Observer{
        public void update(String message);
    }
    //具体观察者, 微信用户是观察者, 里面实现了更新的方法
    public class WeiXinUser impelements Observer{
        //微信用户名
        private String name;
        public WeiXinUser(String name){
            this.name =name;
        }
        @Override
        public void update(String message){
            System.out.println(name+"-"+message);
        }
    
    }
    //抽象被观察者  提供了attach、detach、notify 三个方法
    public interface Subject{
        //增加订阅者
        public void attach(Observer observer);
        //删除订阅者
        public void detach(Observer observer);
        //通知订阅者更新信息
        public void notify(String message);
    }
    //具体被观察者,微信公众号就是具体被观察者,里面存储了订阅该公众号的微信用户,并实现了抽象主题中的方法
    public class SubscriptionSubject impelements Subject{
        //存储订阅公众号的微信用户
        private List<Observer> weixinUserList = new ArrayList<Observer>();
        
        @Override
        public void attach(Observer observer){
            weixinUserList.add(observer);
        }
        @Override
        public void detach(Observer observer){
            weixinUserList.remove(observer);
        }
        @Override
        public void notify(String message){
            for(Observer observer : weixinUserList){
                observer.notify(message);
            }
        }
    }
    //客户端调用
    public class Client{
        public static void main(String[]args){
            SubscriptionSubject mSubscriptionSubject =new SubscriptionSubject();
            //创建微信用户
            WeiXinUser user1 =new WeiXinUser("用户1");
            WeiXinUser user1 =new WeiXinUser("用户2");
            WeiXinUser user1 =new WeiXinUser("用户3");
            //订阅公众号
            mSubscriptionSubject.attach(user1);
            mSubscriptionSubject.attach(user2);
            mSubscriptionSubject.attach(user3);
            //公众号更新发出消息给订阅的微信用户
            mSubscriptionSubject.notify("有新文章了");
            
        }
    }
    
    • 使用场景
      关联行为场景,需要注意的是,关联行为是可拆分的,而不是“组合”关系
      事件多级触发场景,跨系统的消息交换场景,如消息队列、事件总线的处理机制。

    • 优点:
      观察者和被观察者之间是抽象耦合,容易扩展,方便建立一套触发机制。

    • 缺点:
      子啊应用观察者模式时需要考虑一下开发效率和运行效率的问题。程序中包括一个被观察者、多个观察者,
      开发、调试等内容会比较复杂,而且在Java中消息的通知一般是顺序执行的,那么一个观察者卡顿,会影响整体的执行效率,
      在这种情况下一般会采用异步方式。

    相关文章

      网友评论

        本文标题:单例模式、工厂方法模式、代理模式、装饰者模式、观察者模式

        本文链接:https://www.haomeiwen.com/subject/ecfprltx.html