• 设计原则与设计模式


    1. 目录

    2. 简介

    本文是《深入浅出设计模式》的读书笔记
    下文说到的接口分为广义接口和狭义接口。
    广义接口包括接口和抽象类。而狭义接口则单指编程语言中的接口(interface)。

    2.1. 设计原则

    2.1.1. 变化与不变化

    找出应用中可能需要变化之处,把他们独立出来,不要和那些不需要变化的代码混合在一起。
    把会变化的部分取出并封装起来,一边以后可以轻易地改动或扩充此部分,这样就不会影响到不需要改动的部分了。

    2.1.2. 针对接口编程而不是针对实现编程

    这里的接口是广义的接口。
    面向接口编程是指设计类时,把实现的细节放在接口中,把一些实质动作,会改变的动作抽象成接口,然后在接口中实现,因为这些实现细节是很容易改变的有,因此我们要把他组件化,使其可复用。比如实现“小明在吃苹果”可以针对接口实现“{人接口}在{动作接口}{对象接口}”,然后在三个接口中分别实现小明类,吃动作类,苹果类。以后若还有类似需求,比如小明在切苹果。只要实现切动作类即可。具体的例子看下面。

    比如《深入浅出设计模式》中第16页的例子。
    要实现鸭子类的“叫”动作和“飞”动作。
    如果是面向实现编程则简单的硬编码即可。
    而如果面向接口编程则是,定义“叫动作”接口和“飞动作”接口,根据鸭子种类不同(绿头鸭嘎嘎叫,会飞,木头鸭不会叫不会飞,充气鸭吱吱叫不会飞)来组合,使代码可复用,不用一次次定义很多鸭子类。如下:

    public class Duck{
        QuackBehavior qb; //叫动作接口成员
        FlyBehavior fb;   //飞动作接口成员
        public void quack(){  //叫动作函数
            qb.quack();
        }
        public void fly(){ //飞动作函数
            fb.fly();
        }
    }
    
    //实现叫动作接口示例
    //木头鸭不会叫
    public class SlientQuack implement QuackBehavior{
        public void quack(){
            system.out.printout("I can not quack");
        }
    }
    
    //创建木头鸭子类
    public class WoolDuck extend Dcuk{
        public WoolDuck(){
            this.qb= new SlientQuack();
            this.fb = new CanNotFly();
            ....
        }
    }
    

    这样一来,在增加新鸭子类的时候,可以通过硬编码方式静态添加,也可以通过构造函数,传入接口动作参数动态生成新的鸭子类。
    这样就把实现的细节委托给了接口对象。

    2.1.3. 为了交互对象之间的松耦合设计而努力

    这能简历有弹性的OO系统,能够应付变化,因为对象之间的相互依赖降到了最低。

    2.1.4. 开放-关闭模式

    对修改关闭,对扩展开放。

    一个优秀的架构应该具有良好的扩展性,而对修改关闭,以避免修改后引入新bug和损坏依赖。
    使用装饰器可以满足这个设计原则。扩展时先考虑组合,在考虑继承。

    2.1.5. 依赖倒置原则

    要依赖抽象而不是依赖具体类。
    这个原则很像“面向接口编程”原则,在编程中,我们不能让高层组件直接依赖底层组件,不管高层底层,都应该依赖抽象。这个原则工厂模式总得到了体现。
    依赖倒置中的倒置意思见下图:
    依赖倒置

    将高层组件所依赖的组件抽象成抽象类或接口,而底层组件的调用也返回一个抽象类,或者实现这个抽象类。这样就能实现依赖倒置,并且解耦。
    看下工厂模式的部分类图
    工厂
    工厂方法和具体产品都依赖产品这个抽象类或接口,也就是解耦合,我们可以不修改代码,就能添加新的子产品类,只要这个类实现了产品类接口。

    2.1.6. 单一职责

    一个类应该只有一个引起变化的原因。

    3. 观察者模式

    观察者模式

    在Subject中有一个成员变量是Observer类型的数组,里面包含着所有“观察”着Subject的观察者。所以两个接口之间是1对N的聚合关系。

    而ConcreteSub和ConcreteObserver是一个动态关联的关系,因为ConcreteObserver的update方法中有Subject类的参数,以此来分辨是哪个ConcreteSubject发来的更新操作。(一个观察者可以观察多个对象)。

    3.1. 通知的模式

    通知的模式分为push和pull,通常来说push模式更广泛一点,pull会因为多个观察者频发送request而造成网络拥堵。
    但pull也有特点,可以让观察者自定义因素询问操作,比如在subject内声明一个changed变量,subject变化不大时,其值为false,只有当observer对象pull时,且change为真,subject才会发出回应,从而使得observer真正执行update操作。

    4. 装饰者模式

    装饰者

    有时,“继承”这个OOP特性会被滥用,用于构建出一个个和而不同的类,而装饰者模式则可以有效地解决“继承滥用”的问题。装饰者模式使用了“组合”而不是“继承”。
    它动态地给一个对象添加职责,以区分和其他类的不同。

    通过装饰者模式,还可以解决开放-关闭原则问题,因为装饰者模式并没有修改源代码,而是扩展原来的类。

    在Decorator类对象中,会有一个成员变量指向被装饰的对象。这与适配器模式和代理模式相似。
    与适配器模式相比较,适配器模式是更改接口,以对接另一个系统或对象。而装饰器模式不改变接口且添加新职责。
    与代理模式相比较,装饰器模式更强调“装饰”或“增强”原来的类,而代理模式强调对原来的类做“权限管理”或“访问控制”等,而且做这些“新功能”的主体是代理类。本质来说就是意图不一样。

    5. 适配器模式

    适配器

    适配器将一个接口转成另一个接口,使接口不兼容的那些类可以一起工作。

    Adaptee本身并不满足Target接口的要求,所以要借助Adapter(适配器)。像Decoratot一样,在Adapter内也维护这一个成员变量指向原来的对象。

    调用Adapter的request其实就是调用Adaptee的specilRequest()。

    适配器又分为对象适配器和类适配器。
    如果要使用类适配器的话,则要求编程语言支持多继承,或者Target是一个狭义接口Adaptee是个类才行。
    而对象适配器则只需要实现Target这个广义接口既可以。

    6. 代理模式

    代理模式

    proxy对象持有对realSubject的引用,所以在必要的时候可以请求发给realSubject。比如,在使用虚拟代理时,对象已经被创建/加载出来了,这个时候就没必要用虚拟代理回应了。使用保护代理时,访问者有足够的权限,就没必要拒绝访问了。
    代理模式分为以下三个用途:

    • 远程代理,控制访问对象。
    • 虚拟代理,控制访问创建开销大的资源,lazy load。
    • 保护代理,基于权限控制对资源的访问。

    虚拟代理就比如加载网站时,对一些大资源图片等,加载未完成时,浏览器会使用虚拟代理类先给页面返回“加载中,请稍后”等类似图片,一旦大资源被加载了就立即替换。
    代理模式和装饰模式都有“修饰”原有对象的作用。两者的区别可以在看下装饰器小节中的总结。总之就是代理模式和装饰模式的意图不一样,代理是用代理类来委托工作,而装饰是为了增强原先的类。

    7. 工厂模式

    7.1. 简单工厂模式

    简单工厂模式与其说是一种设计模式不如说他是编程习惯。
    通过传入参数,分支判断,创建哪个产品类。
    代码如下:

    
    public class simpleFactory(string productType){
        if(productType == "A"){
            product p = new ProductA();
            return p;
        }
        if(productType == "B"){
            product p = new ProductB();
            return p;
        }
        .....
    }
    

    类图如下:
    简单工厂

    7.2. 工厂模式

    三种工厂模式作为构建型设计模式,他们的精髓在于,调用者不需要知道自己创建的子类类型,只需要通过工厂类调用即可。

    工厂模式让子类决定要实例化的类是哪一个,通过子类的factoryMethod()返回一个该子类的对象。也就是把创建对象的过程交给了子类。你选择了哪个子类就自然用哪个子类的factoryMethod()方法。
    也可以理解把简单工厂中的臃肿的工厂方法拆分给了每个产品,从而有了产品自己的工厂类,同时也让代码中的类变多了,每个产品除了自己之外,还要配套一个自己的工厂类。
    类图下方:

    工厂模式

    这样的模式优点是,在新增子类的时候,不需要像简单工厂模式那样修改主逻辑(分支判断),而是直接在子类中添加工厂模式。这就满足了“开放-关闭”原则。

    简单工厂与工厂模式的区别
    简单工厂在一个地方吧全部事情做完,如果要添加新的种类则需要修改之前的代码,而工厂模式则是打了一个框架,让子类添加自己的工厂类。比起简单工厂,工厂模式更有弹性。

    
    // 产品接口
    public abstract Product(){
        public Product factoryMethod();
        ....
    }
    
    // 产品实体类
    public class ProductA extends Product{
    
    }
    public class ProductB extends Product{
    
    }
    
    // 工厂接口
    public abstract class Factory{、
        public Product createProduct(string type);
    }
    
    // 具体A产品工厂类
    public class ProductFactoryA extends Factory {
        public Product factoryMethod() {
            return new ProductA();
        }
    }
    
    // 具体B产品工厂类
    public class ProductFactoryB extends Factory {
        public Product factoryMethod() {
            return new ProductB();
        }
    }
    
    // 主函数调用
        Factory factory = null;
        // 生成A
        factory = new ProductFactoryA();
        Product p = facroty.factoryMethod();
        // 生成B
        factory = new ProductFactoryB();
        Product p = facroty.factoryMethod();
    

    8. 抽象工厂模式

    抽象工厂模式为创建一组对象提供了一种解决方案。与工厂方法模式相比,抽象工厂模式中的具体工厂不只是创建一种产品,它负责创建一族产品。

    抽象工厂提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。

    抽象工厂是对工厂类进行归纳升华,工厂类是抽象工厂类的子类。

    抽象工厂

    //省略掉了产品AB的声明,和工厂模式的一样,主要讲工厂类的不一样
    
    //创建产品族的工厂接口
    public interface Factory {
    	
    	public ProductA createProductA();
    	
    	public ProductB createProductB();
     
    }
    
    // 生成1版本产品的工厂
    public class ProductFactory1 implements Factory {
     
    	@Override
    	public ProductA createProductA() {
    		return new ConcreteProductA1();
    	}
     
    	@Override
    	public ProductB createProductB() {
    		return new ConcreteProductB1();
    	}
     
    }
    
    //生成2版本产品的工厂
    public class ProductFactory2 implements Factory {
     
    	@Override
    	public ProductA createProductA() {
    		return new ConcreteProductA2();
    	}
     
    	@Override
    	public ProductB createProductB() {
    		return new ConcreteProductB2();
    	}
     
    }
    
    
    // 主函数调用
            Factory factory1 = null;
    		System.out.println("工厂1 :生产1版本产品AB");
    		factory1 = new ProductFactory1();
    		factory1.createProductA();
    		factory1.createProductB();
    
            Factory factory2 = null;
    		System.out.println("工厂2 :生产2版本产品AB");
    		factory1 = new ProductFactory2();
    		factory1.createProductA();
    		factory1.createProductB();
    
    

    9. 单例模式

    有些对象只需要一个比如线程池和缓存,任务管理器等。

    在多线程的时候,很容易创建出多个单例对象,这样就会造成很严重的隐患。

    单例对象的创建方法有两种:

    懒汉模式和饿汉模式,懒汉模式是通过双层加锁,而饿汉模式是通过静态声明。
    懒汉模式的效率比饿汉模式低,但注意的是,在多个类加载器的时候,使用饿汉模式需要指定同一个类加载器,不然也会声明出多个单例对象。

    10. 命令模式

    将请求封装成对象,以便使用不同的请求,队列或者日志来参数化其他对象。命令模式也支持可撤销的操作。

    用于记录一段期间内命令,然后恢复。如死机后恢复,或者redis的持久化等。

    命令模式

    11. 责任链

    命令模式最常见的就是java中的try-catch代码块。排除一个个错误类型,然后找到合适的处理分支。

  • 相关阅读:
    ctf web 百度杯”CTF比赛 九月场Upload i春秋
    ctf web 西普实验吧 登陆一下好吗 MySQL隐式转化 MySQL表达式的计算顺序
    Firefox 47.0.1
    给数组原型添加方法
    JS中几种常见的数组算法(前端面试必看)
    进制转换技巧解析
    redis通过6379端口无法连接服务器
    阿里云图片或文件上传 启动时报Error creating bean with name 'ossClient'问题
    20170628-三七互娱-测试工程师(提前批)
    20170514-vivo-软件工程师Java(提前批)
  • 原文地址:https://www.cnblogs.com/Jun10ng/p/12975196.html
Copyright © 2020-2023  润新知