精华内容
下载资源
问答
  • 常用设计模式总结

    万次阅读 多人点赞 2019-07-31 19:13:12
    设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结,是可复用面向对象软件的基础。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 ...

    设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结,是可复用面向对象软件的基础。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。项目中合理的运用设计模式可以完美的解决很多问题,每种模式在现在中都有相应的原理来与之对应,每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是它能被广泛应用的原因。java设计模式广泛认同的有23种,本文介绍最常用的、最会被问到的设计模式。
    #一、设计模式的分类
    总体来说23种设计模式分为三大类:
    创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
    结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
    行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
    #二、设计模式的六大原则
    总原则-开闭原则
    对扩展开放,对修改封闭。在程序需要进行拓展的时候,不能去修改原有的代码,而是要扩展原有代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。
    想要达到这样的效果,我们需要使用接口和抽象类等,后面的具体设计中我们会提到这点。
    1、单一职责原则
    不要存在多于一个导致类变更的原因,也就是说每个类应该实现单一的职责,否则就应该把类拆分。
    2、里氏替换原则(Liskov Substitution Principle)
    任何基类可以出现的地方,子类一定可以出现。里氏替换原则是继承复用的基石,只有当衍生类可以替换基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。
    里氏代换原则是对“开-闭”原则的补充。实现“开闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。里氏替换原则中,子类对父类的方法尽量不要重写和重载。因为父类代表了定义好的结构,通过这个规范的接口与外界交互,子类不应该随便破坏它。
    3、依赖倒转原则(Dependence Inversion Principle)
    面向接口编程,依赖于抽象而不依赖于具体。写代码时用到具体类时,不与具体类交互,而与具体类的上层接口交互。
    4、接口隔离原则(Interface Segregation Principle)
    每个接口中不存在子类用不到却必须实现的方法,如果不然,就要将接口拆分。使用多个隔离的接口,比使用单个接口(多个接口方法集合到一个的接口)要好。
    5、迪米特法则(最少知道原则)(Demeter Principle)
    一个类对自己依赖的类知道的越少越好。无论被依赖的类多么复杂,都应该将逻辑封装在方法的内部,通过public方法提供给外部。这样当被依赖的类变化时,才能最小的影响该类。
    最少知道原则的另一个表达方式是:只与直接的朋友通信。类之间只要有耦合关系,就叫朋友关系。耦合分为依赖、关联、聚合、组合等。我们称出现为成员变量、方法参数、方法返回值中的类为直接朋友。局部变量、临时变量则不是直接的朋友。我们要求陌生的类不要作为局部变量出现在类中。
    6、合成复用原则(Composite Reuse Principle)
    尽量首先使用合成/聚合的方式,而不是使用继承。
    #常用设计模式
    ##1、工厂方法模式
    工厂方法模式分为三种:
    1.1、普通工厂模式,就是建立一个工厂类,对实现了同一接口的一些类进行实例的创建。
    举例如下:(我们举一个发送邮件和短信的例子)

    public interface Sender {  
        public void Send();  
    }  
    public class MailSender implements Sender {  
        @Override  
        public void Send() {  
            System.out.println("this is mailsender!");  
        }  
    }  
    public class SmsSender implements Sender {  
      
        @Override  
        public void Send() {  
            System.out.println("this is sms sender!");  
        }  
    }  
    public class SendFactory {  
      
        public Sender produce(String type) {  
            if ("mail".equals(type)) {  
                return new MailSender();  
            } else if ("sms".equals(type)) {  
                return new SmsSender();  
            } else {  
                System.out.println("请输入正确的类型!");  
                return null;  
            }  
        }  
    }  
    public class FactoryTest {  
      
        public static void main(String[] args) {  
            SendFactory factory = new SendFactory();  
            Sender sender = factory.produce("sms");  
            sender.Send();  
        }  
    }  
    

    输出:this is sms sender!
    2.2、多个工厂方法模式,是对普通工厂方法模式的改进,在普通工厂方法模式中,如果传递的字符串出错,则不能正确创建对象,而多个工厂方法模式是提供多个工厂方法,分别创建对象。
    将上面的代码做下修改,改动下SendFactory类就行,如下:

    public class SendFactory {  
          
        public Sender produceMail(){  
            return new MailSender();  
        }  
          
        public Sender produceSms(){  
            return new SmsSender();  
        }  
    }  
    public class FactoryTest {  
      
        public static void main(String[] args) {  
            SendFactory factory = new SendFactory();  
            Sender sender = factory.produceMail();  
            sender.Send();  
        }  
    }  
    

    输出:this is mailsender!
    3.3、静态工厂方法模式,将上面的多个工厂方法模式里的方法置为静态的,不需要创建实例,直接调用即可。

    public class SendFactory {  
          
        public static Sender produceMail(){  
            return new MailSender();  
        }  
          
        public static Sender produceSms(){  
            return new SmsSender();  
        }  
    }  
    public class FactoryTest {  
      
        public static void main(String[] args) {      
            Sender sender = SendFactory.produceMail();  
            sender.Send();  
        }  
    }  
    

    输出:this is mailsender!
    总体来说,工厂模式适合:凡是出现了大量的产品需要创建,并且具有共同的接口时,可以通过工厂方法模式进行创建。在以上的三种模式中,第一种如果传入的字符串有误,不能正确创建对象,第三种相对于第二种,不需要实例化工厂类,所以,大多数情况下,我们会选用第三种——静态工厂方法模式。
    ##2、抽象工厂模式
    工厂方法模式有一个问题就是,类的创建依赖工厂类,也就是说,如果想要拓展程序,必须对工厂类进行修改,这违背了闭包原则,所以,从设计角度考虑,有一定的问题,如何解决?就用到抽象工厂模式,创建多个工厂类,这样一旦需要增加新的功能,直接增加新的工厂类就可以了,不需要修改之前的代码。

    public interface Sender {  
        public void Send();  
    }  
    public class MailSender implements Sender {  
        @Override  
        public void Send() {  
            System.out.println("this is mailsender!");  
        }  
    }  
    public class SmsSender implements Sender {  
      
        @Override  
        public void Send() {  
            System.out.println("this is sms sender!");  
        }  
    }  
    public class SendMailFactory implements Provider {  
          
        @Override  
        public Sender produce(){  
            return new MailSender();  
        }  
    }  
    public class SendSmsFactory implements Provider{  
      
        @Override  
        public Sender produce() {  
            return new SmsSender();  
        }  
    }  
    public interface Provider {  
        public Sender produce();  
    }  
    public class Test {  
      
        public static void main(String[] args) {  
            Provider provider = new SendMailFactory();  
            Sender sender = provider.produce();  
            sender.Send();  
        }  
    }  
    

    其实这个模式的好处就是,如果你现在想增加一个功能:发及时信息,则只需做一个实现类,实现Sender接口,同时做一个工厂类,实现Provider接口,就OK了,无需去改动现成的代码。这样做,拓展性较好!
    ##3、单例模式
    java中单例模式是一种常见的设计模式,单例模式的写法有好几种,这里主要介绍经常被面试的经典的两种:懒汉模式、饿汉模式。
    单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。比如每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。
    ###3.1、懒汉式单例

    //懒汉式单例类.在第一次调用的时候实例化自己 
    public class Singleton {
        private Singleton() {}
        private static Singleton single=null;
        //静态工厂方法 
        public static Singleton getInstance() {
             if (single == null) {  
                 single = new Singleton();
             }  
            return single;
        }
    } 
    

    Singleton通过将构造方法限定为private避免了类在外部被实例化,在同一个虚拟机范围内,Singleton的唯一实例只能通过getInstance()方法访问。
    (事实上,通过Java反射机制是能够实例化构造方法为private的类的,那基本上会使所有的Java单例实现失效。此问题在此处不做讨论,姑且掩耳盗铃地认为反射机制不存在。)
    但是以上懒汉式单例的实现没有考虑线程安全问题,它是线程不安全的,并发环境下很可能出现多个Singleton实例,要实现线程安全,有以下三种方式,都是对getInstance这个方法改造,保证了懒汉式单例的线程安全。
    1、在getInstance方法上加同步

    public static synchronized Singleton getInstance() {
             if (single == null) {  
                 single = new Singleton();
             }  
            return single;
    }
    

    2、双重检查锁定

    public static Singleton getInstance() {
            if (singleton == null) {  
                synchronized (Singleton.class) {  
                   if (singleton == null) {  
                      singleton = new Singleton(); 
                   }  
                }  
            }  
            return singleton; 
    }
    

    3、静态内部类

    public class Singleton {  
        private static class LazyHolder {  
           private static final Singleton INSTANCE = new Singleton();  
        }  
        private Singleton (){}  
        public static final Singleton getInstance() {  
           return LazyHolder.INSTANCE;  
        }  
    }  
    

    这种方式是当被调用getInstance()时才去加载静态内部类LazyHolder,LazyHolder在加载过程中会实例化一个静态的Singleton,因为利用了classloader的机制来保证初始化instance时只有一个线程,所以Singleton肯定只有一个,是线程安全的,这种比上面1、2都好一些,既实现了线程安全,又避免了同步带来的性能影响。

    ###3.2、饿汉式单例

    //饿汉式单例类.在类初始化时,已经自行实例化 
    public class Singleton1 {
        private Singleton1() {}
        private static final Singleton1 single = new Singleton1();
        //静态工厂方法 
        public static Singleton1 getInstance() {
            return single;
        }
    }
    

    饿汉式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的。

    ###3.3、饿汉式和懒汉式区别
    从名字上来说,饿汉和懒汉,
    饿汉就是类一旦加载,就把单例初始化完成,保证getInstance的时候,单例是已经存在的了,
    而懒汉比较懒,只有当调用getInstance的时候,才回去初始化这个单例。
    另外从以下两点再区分以下这两种方式:
    1、线程安全:
    饿汉式天生就是线程安全的,可以直接用于多线程而不会出现问题,
    懒汉式本身是非线程安全的,为了实现线程安全有几种写法,分别是上面的1、2、3,这三种实现在资源加载和性能方面有些区别。
    2、资源加载和性能:
    饿汉式在类创建的同时就实例化一个静态对象出来,不管之后会不会使用这个单例,都会占据一定的内存,但是相应的,在第一次调用时速度也会更快,因为其资源已经初始化完成,
    而懒汉式顾名思义,会延迟加载,在第一次使用该单例的时候才会实例化对象出来,第一次调用时要做初始化,如果要做的工作比较多,性能上会有些延迟,之后就和饿汉式一样了。
    至于1、2、3这三种实现又有些区别,
    第1种,在方法调用上加了同步,虽然线程安全了,但是每次都要同步,会影响性能,毕竟99%的情况下是不需要同步的,
    第2种,在getInstance中做了两次null检查,确保了只有第一次调用单例的时候才会做同步,这样也是线程安全的,同时避免了每次都同步的性能损耗
    第3种,利用了classloader的机制来保证初始化instance时只有一个线程,所以也是线程安全的,同时没有性能损耗,所以一般我倾向于使用这一种。
    综上大部分情况下单例模式中最好的实现方式就是静态内部类的懒汉模式。
    ##4、建造者模式
    一、场景
    当需要生产一辆汽车时,我们需要为其装配发动机、轮胎、座椅等等部件,这个装配过程是比较复杂的而且也需要较高的组装技术。而建造者模式(Builder Pattern)就是为了将部件与组装分离开。
    二、 概念
      将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。
      与抽象工厂的区别:在建造者模式里,有个指导者,由指导者来管理建造者,用户是与指导者联系的,指导者联系建造者最后得到产品。即建造模式可以强制实行一种分步骤进行的建造过程。
      建造模式是将复杂的内部创建封装在内部,对于外部调用的人来说,只需要传入建造者和建造工具,对于内部是如何建造成成品的,调用者无需关心。
    三、建造者模式结构组成
    Product: 表示被构造的复杂对象,其中包含需要构建的部件属性。
    Builder: 创建一个产品对象的各个部件指定抽象接口。
    ConcreteBuilder: 实现Builder的接口以构造和装配该产品的各个部件,定义并明确它所创建的表示。
    Director: 调用具体建造者角色以创建产品对象。
    下面以构建一辆汽车为例,写示例代码:
    1.Product角色:组装一辆汽车首先的有各种配件,如发动机、轮胎、座椅等。

    public class Car{
        public String engine;
        public String tyre;
        public String seat;
        public Car(){
    
        }
    
        public String getEngine() {
            return engine;
        }
        public void setEngine(String engine) {
            this.engine = engine;
        }
        public String getTyre() {
            return tyre;
        }
        public void setTyre(String tyre) {
            this.tyre = tyre;
        }
        public String getSeat() {
            return seat;
        }
        public void setSeat(String seat) {
            this.seat = seat;
        }
    
    }
    

    2.Builder角色:知道了所需配件后,就需要生产配件了,定义一个生产配件的抽象建造者接口。

    public interface Builder {
        String buildEngine();
        String buildTyre();
        String buildSeat();
    }  
    

    3.ConcreteBuilder角色:实现抽象的 建造者接口生成具体的建造者,并开始生产具体的配件。

    public class CarBuilder implements Builder{
    
        @Override
        public String buildEngine() {
            // 生产发动机
            return "发动机";
        }
    
        @Override
        public String buildTyre() {
            // 生产轮胎
            return "轮胎";
        }
    
        @Override
        public String buildSeat() {
            // 生产座椅
            return "座椅";
        }
    }
    
    }   
    

    4.Director角色:在生产出配件后,由指导者指导组装配件生成汽车。

    public class CarDirector {
        CarBuilder cb;
        public CarDirector(CarBuilder cb){
            this.cb=cb;
        }
        public Car constructCar(){
            Car car=new Car();
            car.setEngine(cb.buildEngine());
            car.setTyre(cb.buildTyre());
            car.setSeat(cb.buildSeat());
            return car;
        }
    }
    

    5.最终得到一辆汽车:

    public class Client {
    
        public static void main(String[] args) {
            CarDirector carDirector=new CarDirector(new CarBuilder());
            Car car=carDirector.constructCar();
            System.out.println(car.getEngine()+car.getTyre()+car.getSeat());
    
        }
    }
    

    ##5、适配器模式
    适配器模式,把一个类接口变化成客户端所期待的另一个类的接口,使原来因接口不匹配而无法一起工作的类能够一起工作。Java源码中的例子:如Java IO中的java.io.InputStreamReader(InputStream) 和java.io.OutputStreamWriter(OutputStream)就是典型的适配器模式,通过InputStreamReader、OutputStreamWriter适配器将字节流转换为字符流。
    ###5.1、类适配器
    Target(目标角色): 客户所期待的接口。目标可以是具体的或抽象的类,也可以是接口。
    Adaptee(源角色):现在需要适配的类。
    Adapter(适配器): 适配器类是本模式的核心。适配器把源接口转换成目标接口。显然,这一角色不可以是接口,而必须是具体类。

    /**
     * 目标角色,如举例中需要转换成的三相插头
     *  @author  14501_000
     */
    public interface Target {
         void handleReq();
    }   
    
    /**
     * 源角色,需要被适配的类,如举例中的两脚插头
     */
    public class Adaptee {
         public void request(){
            System.out.println( "可以完成客户请求的需要的功能!" );
        }
    }   
    
    /**
     * 适配器,把源接口转换成目标接口,即将两脚插头转换为三脚插头
     *
     */
    public class Adapter extends Adaptee implements Target{
         public void handleReq() {
             super.request();
        }
         }   
    
    /**
     * 客户端类,通过三脚插座进行工作
     *
     */
    public class Client {
         public void work(Target t){
             t.handleReq();
        }
         public static void main(String[] args){
            Client c = new Client();
            Target t = new Adapter();
             c.work(t);
        }
    }
    

    ###5.2、对象适配器
    上面这种实现的适配器称为类适配器,因为 Adapter 类既继承了 Adaptee (被适配类),也实现了 Target 接口(因为 Java 不支持多继承,所以这样来实现),在 Client 类中我们可以根据需要选择并创建任一种符合需求的子类,来实现具体功能。另外一种适配器模式是对象适配器,它不是使用多继承或继承再实现的方式,而是使用直接关联,或者称为委托的方式,通过组合的方式跟适配对象组合。

    /**
     * 适配器,把源接口转换成目标接口,即将两脚插头转换为三脚插头
     *
     */
    public class Adapter implements Target{
        Adaptee adaptee ;
         public Adapter(Adaptee adaptee){
             this.adaptee = adaptee ;
        }
         public void handleReq() {
             adaptee.request();
        }
    
    }
    
    public class Client {
         public void work(Target  t){
             t.handleReq();
        }
         public static void main(String[]  args ) {
            Client c =new Client();
            Adaptee adaptee =new Adaptee();
            Target t = new Adapter(adaptee);
             c.work( t );
        }
    }
    

    测试结果与上面的一致。 使用对象适配器模式,可以使得 Adapter 类(适配类)根据传入的 Adaptee 对象达到适配多个不同被适配类的功能,当然,此时我们可以为多个被适配类提取出一个接口或抽象类。这样看起来的话,似乎对象适配器模式更加灵活一点。
    ###5.3、适配器模式适用场景
    系统需要使用现有的类,而这些类的接口不符合系统的接口。
    想要建立一个可以重用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。
    两个类所做的事情相同或相似,但是具有不同接口的时候。
    旧的系统开发的类已经实现了一些功能,但是客户端却只能以另外接口的形式访问,但我们不希望手动更改原有类的时候。
    ##6、装饰者模式
    装饰(Decorate)模式又称为包装(Wrapper)模式。可以动态的为一个对象增加新的功能。装饰模式是一种用于代替继承的技术,无须通过继承增加子类就能扩展对象的新功能。使用对象的关联关系代替继承关系,更加灵活,同时避免类型体系的快速膨胀。
    举一个简单的汽车例子,创造每一种功能的汽车都需要继承车的父类进行实现,那么当我们需要既能路上行驶又能水上行驶的车又得继续继承父类拓展新的类。所以每增加一种新功能的汽车都需要新增一个类,这样的话就会创建大量的类。这时候就能使用装饰模式了。
    ###6.1、示例
    抽象构件

    public interface AbstractCar {
        void move();
    }  
    

    具体构建

    public class Car implements AbstractCar{
    
        public void move() {
            System.out.println("路上行驶");
        }
    }
    

    装饰角色

    public class SuperCar implements AbstractCar{
        protected AbstractCar car;
        public SuperCar(AbstractCar car){
            this.car=car;
        }
    
        public void move() {
            car.move();
        }
    
    }
    

    具体装饰 角色

    /**
     * 飞行汽车  
    */   
    
    ublic class FlyCar extends SuperCar{
    
        public FlyCar(AbstractCar car) {
            super(car);
        }
    
        public void fly() {
            System.out.println("空中行驶汽车");
        }
    
    
        @Override
        public void move() {
            super.move();
             fly();
        }
    
    }
    
    
    
    /**
     * 水上汽车
     */
    public class WaterCar extends SuperCar{
    
        public WaterCar(AbstractCar car) {
            super(car);
        }
    
        public void swim() {
            System.out.println("水上行驶汽车");
        }
        @Override
        public void move() {
            super.move();
            swim();
        }
    
    }
    

    客户端

    public class Client {
    
        public static void main(String[] args) {
            Car car=new Car();
            car.move();
    
            System.out.println("------增加新功能,飞行------");
            FlyCar flyCar=new FlyCar(car);
            flyCar.move();
    
            System.out.println("------新增加功能,水上行驶-----");
            WaterCar waterCar=new WaterCar(car);
            waterCar.move();
    
            System.out.println("-----新增加两个功能,飞行与水上行驶-----");
            WaterCar waterCar2=new WaterCar(flyCar);
            waterCar2.move();
    
        }
    
    }
    

    //输出结果
    路上行驶
    ------增加新功能,飞行------
    路上行驶
    空中行驶汽车
    ------新增加功能,水上行驶-----
    路上行驶
    水上行驶汽车
    -----新增加两个功能,飞行与水上行驶-----
    路上行驶
    空中行驶汽车
    水上行驶汽车
    由此可知,使用装饰模式就不用创建大量新的类而可以拓展出具有更多功能的对象了。
    ###6.2、装饰模式在Java I/O库中的应用
    IO流实现细节:
    Component抽象构件角色:io流中的InputStream,OutputStream,Reader,Writer
    ConcreteComponent具体构件角色:io流中的FileInputStream,FileOutputStream
    Decorate装饰角色:持有抽象构件的引用,FilterInputStream,FilterOutputStream
    ConcreteDecorate具体装饰角色:负责给构件对象添加新的责任,BufferedInputStream,BufferedOutputStream等
    ##7、代理模式
    为某个对象提供一个代理,从而控制这个代理的访问。代理类和委托类具有共同的父类或父接口,这样在任何使用委托类对象的地方都可以使用代理类对象替代。代理类负责请求的预处理、过滤、将请求分配给委托类处理、以及委托类处理完请求的后续处理。
    ###7.1、静态代理
    由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。
    示例代码:
    1.抽象角色

    public interface AbstractSubject {
         void doSomething();
    }   
    

    2.代理角色

    public class ProxySubject implements AbstractSubject{
         private AbstractSubject  real ;
         public ProxySubject(AbstractSubject  real) {
             this.real=real ;
        }
         @Override
         public void doSomething() {
    
             real.doSomething();
        }
    
         public void doOtherthing() {
    
        }
    }   
    

    3.真实角色

    public class RealSubject implements AbstractSubject{
         @Override
         public void doSomething() {
            System.out.println( "真实角色被使用" );
        }
    }   
    

    4.客户端

    public class Client {
         public static void main(String[]  args ) {
            RealSubject real = new  RealSubject();
            ProxySubject proxy = new  ProxySubject( real );
             proxy.doSomething();
        }
    }   
    

    5.静态代理的优缺点
    优点: 业务类只需要关注业务逻辑本身,保证了业务类的重用性。这是代理的共有优点。
    缺点:代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
    ###7.2、动态代理
    动态代理类的源码是程序在运行期间由JVM根据反射等机制动态生成的,所以不存在代理类的字节码文件。代理角色和真实角色的联系在程序运行时确定。
    1.首先看看和动态代理相关JavaAPI
    ① .java.lang.reflect.Proxy
    这是Java动态代理机制生成的所有代理类的父类,它提供了一组静态的方法来为一组接口动态的生成代理类及其对象。
    Proxy类的静态方法:

    //方法 1: 该方法用于获取指定代理对象所关联的调用处理器  
    static InvocationHandler getInvocationHandler(Object proxy )  
    //方法 2:该方法用于获取关联于指定类装载器和一组接口的动态代理类的类对象  
    static Class getProxyClass(ClassLoader loader,Class[] interfaces)  
    //方法 3:该方法用于判断指定类对象是否是一个动态代理类  
    static boolean isProxyClass(Class cl )    
    //方法 4:该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
    static Object newProxyInstance(ClassLoader loader,  Class[] interfaces, InvocationHandler  h )   
    

    ②.java.lang.reflect.InvocationHandler
    这是调用处理器接口,它自定义了一个 invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。每次生成动态代理类对象时都要指定一个对应的调用处理器对象。
    InvocationHandler核心方法

    //该方法负责集中处理动态代理类上的所有方法调用。第一个参数既是代理类实例,第二个参数是被调用的方法对象  
    //第三个方法是调用参数。调用处理器根据这三个参数进行预处理或分派到委托类实例上反射执行   
    Object invoke(Object proxy, Method  method,Object[] args )     
    

    ③ .java.lang.reflect.ClassLoader
      这是类装载器类,负责将类的字节码装载到 Java 虚拟机(JVM)中并为其定义类对象,然后该类才能被使用。Proxy 静态方法生成动态代理类同样需要通过类装载器来进行装载才能使用,它与普通类的唯一区别就是其字节码是由 JVM 在运行时动态生成的而非预存在于任何一个 .class 文件中。每次生成动态代理类对象时都需要指定一个类装载器对象 。
    2.动态代理实现步骤

    • 实现InvocationHandler接口创建自己的调用处理器 。
    • 给Proxy类提供ClassLoader和代理接口类型数组创建动态代理类 。
    • 执行真实角色具体任务。

    示例代码
    1.抽象角色和真实角色代码与上述相同 。
    2. 创建自己的调用处理器:

    public class SubjectHandler implements InvocationHandler{
        AbstractSubject real;
        public SubjectHandler(AbstractSubject real){
            this.real=real;
        }
    
        @Override
        public Object invoke(Object obj, Method method, Object[] args)throws Throwable {
            System.out.println("代理类预处理任务");
            //利用反射机制将请求分派给委托类处理。Method的invoke返回Object对象作为方法执行结果。  
      //因为示例程序没有返回值,所以这里忽略了返回值处理
            method.invoke(real, args);
            System.out.println("代理类后续处理任务");
            return null;
        }
    
    }
    

    3.客户端 :

    public class Client {
    
        public static void main(String[] args) {
            RealSubject real=new RealSubject();
            SubjectHandler handler=new SubjectHandler(real);
            //生成代理类对象
            AbstractSubject proxy=(AbstractSubject) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{AbstractSubject.class},handler);
            proxy.doSomething();
    
        }
    
    }
    

    4.动态代理的优缺点
    优点:
      动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。
    不足:
      Proxy 已经设计得非常优美,但是还是有一点点小小的遗憾之处,那就是它始终无法摆脱仅支持 interface 代理的桎梏,因为它的设计注定了这个遗憾。回想一下那些动态生成的代理类的继承关系图,它们已经注定有一个共同的父类叫 Proxy。Java 的继承机制注定了这些动态代理类们无法实现对 class 的动态代理,原因是多继承在 Java 中本质上就行不通。
    ##8、策略模式
    下面是某个市场人员接到单后的报价策略,报价策略很复杂,可以简单作如下分类:
    普通客户小批量,不打折
    普通客户大 批量,打9折
    老 客户小批量,打8.5折
    老 客户大批量,打8折
    我们通常可以适用条件语句进行处理,代码如下:

    public class Test {
        public double getPrice(String type ,double price ){
            if(type.equals("普通客户小批量")){
                System.out.println("不打折");
                return price;
            }else if(type.equals("普通客户大批量")){
                System.out.println("打9折");
                return price*0.9;
    
            }else if(type.equals("老客户小批量")){
                System.out.println("打8.5折");
                return price*0.85;
    
            }else if(type.equals("老客户大批量")){
                System.out.println("打8折");
                return price*0.8;
            }
            return price;
        }
    }
    

    这样实现起来比较简单,符合一般开人员的思路,但是当类型特别多,算法比较复杂时,整个条件控制代码会变得很长,难于维护。这时我们可以采用策略模式,将不同的策略分开,委派给不同的对象管理。
    使用策略模式实现:

    public interface Strategy {
        /**
         * 
         * @param price 商品原价
         * @return 打折后价格
         */
        public double getPrice(double price);
    }
    
    
    public class NewCustomerFewStrategy implements Strategy{
    
        public double getPrice(double price) {
            System.out.println("普通客户小批量,不打折");
            return price;
        }
    }
    
    public class NewCustomerManyStrategy implements Strategy{
    
        public double getPrice(double price) {
            System.out.println("普通客户大批量,打9折");
            return price*0.9;
        }
    }
    
    public class OldCustomerFewStrategy implements Strategy{
    
        public double getPrice(double price) {
            System.out.println("老客户小批量,打8.5折");
            return price*0.85;
        }
    }
    
    public class OldCustomerManyStrategy implements Strategy{
    
        public double getPrice(double price) {
            System.out.println("老客户大批量,打8折");
            return price;
        }
    } 
    
    
    public class Context {
        private Strategy strategy;//持有策略引用
    
        public Context(Strategy strategy) {
            super();
            this.strategy = strategy;
        }
        public void printPrice(double price ){
            System.out.println("价格为:"+strategy.getPrice(price));
        }
    }
    
    
    
    public class Client {
         public static void main(String[] args) {
            Strategy strategy= new NewCustomerFewStrategy();
            Context context= new Context(strategy);
            context.printPrice(100);
        }
    
    }  
    
    //输出结果
    //普通客户小批量,不打折
    //价格为:100.0  
    
    

    从上面的示例可以看出,策略模式仅仅封装算法,提供新的算法插入到已有系统中,以及老算法从系统中“退休”的方法,策略模式并不决定在何时使用何种算法。在什么情况下使用什么算法是由客户端决定的。
    优点:
    策略模式提供了管理相关的算法族的办法。策略类的等级结构定义了一个算法或行为族。恰当使用继承可以把公共的代码移到父类里面,从而避免代码重复。
    使用策略模式可以避免使用多重条件(if-else)语句。多重条件语句不易维护,它把采取哪一种算法或采取哪一种行为的逻辑与算法或行为的逻辑混合在一起,统统列在一个多重条件语句里面,比使用继承的办法还要原始和落后。
    缺点:
    客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。换言之,策略模式只适用于客户端知道算法或行为的情况。
    由于策略模式把每个具体的策略实现都单独封装成为类,如果备选的策略很多的话,那么对象的数目就会很可观。
    ##9、模板方法模式
    模板方法模式是类的行为模式。准备一个抽象类,将部分逻辑以具体方法以及具体构造函数的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现。这就是模板方法模式的用意。
    比如定义一个操作中的算法的骨架,将步骤延迟到子类中。模板方法使得子类能够不去改变一个算法的结构即可重定义算法的某些特定步骤。
    模式中的角色:

    • 抽象类(AbstractClass):实现了模板方法,定义了算法的骨架。
    • 具体类(ConcreteClass):实现抽象类中的抽象方法,已完成完整的算法。

    以准备去学校所要做的工作(prepareGotoSchool)为例,假设需要分三步:穿衣服(dressUp),吃早饭(eatBreakfast),带上东西(takeThings)。学生和老师要做得具体事情肯定有所区别。
    抽象类AbstractClass

    public abstract class AbstractPerson{  
         //抽象类定义整个流程骨架  
         public void prepareGotoSchool(){  
              dressUp();  
              eatBreakfast();  
              takeThings();  
         }  
         //以下是不同子类根据自身特性完成的具体步骤  
         protected abstract void dressUp();  
         protected abstract void eatBreakfast();  
         protected abstract void takeThings();  
    } 
    

    具体类ConcreteClass

    public class Student extends AbstractPerson{  
         @Override  
         protected void dressUp() {  
              System.out.println(“穿校服");  
         }  
         @Override  
         protected void eatBreakfast() {  
              System.out.println(“吃妈妈做好的早饭");  
         }  
      
         @Override  
         protected void takeThings() {  
              System.out.println(“背书包,带上家庭作业和红领巾");  
         }  
    }  
    
    public class Teacher extends AbstractPerson{  
         @Override  
         protected void dressUp() {  
              System.out.println(“穿工作服");  
         }  
         @Override  
         protected void eatBreakfast() {  
              System.out.println(“做早饭,照顾孩子吃早饭");  
         }  
      
         @Override  
         protected void takeThings() {  
              System.out.println(“带上昨晚准备的考卷");  
         }  
    }  
    
    public class Client {  
         public static void main(String[] args) {  
         Student student = new Student()  
         student.prepareGotoSchool();  
      
         Teacher teacher  = new Teacher()  
         teacher.prepareGotoSchool();  
         }  
    }  
    

    优点:
    模板方法模式通过把不变的行为搬移到超类,去除了子类中的重复代码。
    子类实现算法的某些细节,有助于算法的扩展。
    通过一个父类调用子类实现的操作,通过子类扩展增加新的行为,符合“开放-封闭原则”。
    缺点:
    每个不同的实现都需要定义一个子类,这会导致类的个数的增加,设计更加抽象。
    适用场景:
    在某些类的算法中,用了相同的方法,造成代码的重复。
    控制子类扩展,子类必须遵守算法规则。
    ##10、观察者模式
    观察者模式定义了一个一对多的依赖关系,让多个观察者对象同时监听同一个主题对象。当这个主题状态发生改变时,会通知所有观察者对象,让它们自动更新自己。
    ###10.1、模型结构

    • 抽象主题角色(Subject): 把所有对观察者对象的引用保存在一个集合中,每个抽象主题角色都可以有任意数量的观察者。抽象主题提供一个接口,可以增加和删除观察者角色。一般用一个抽象类和接口来实现。
    • 具体主题角色(ConcreteSubject): 在具体主题内部状态改变时,给所有登记过的观察者发出通知。具体主题角色通常用一个子类实现。
    • 抽象观察者角色(Observer): 为所有具体的观察者定义一个接口,在得到主题的通知时更新自己。
    • 具体观察者角色(ConcreteObserver): 该角色实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。通常用一个子类实现。如果需要,具体观察者角色可以保存一个指向具体主题角色的引用。
      这里写图片描述

    ###10.2、代码示例
    抽象主题角色:

    public class Subject {
        protected List<Observer> list=new ArrayList<Observer>();
    
        public void registerObserve(Observer obs){
            list.add(obs);
        }
    
        public void removeObserve(Observer obs){
            list.remove(obs);
        }
    
        //通知所有的观察者更新状态
        public void notifyAllObserve(){
            for(Observer obs:list){
                obs.update(this);
            }
        }
    }
    

    具体 主题角色:

    public class ConcreteSubject extends Subject{
        private int state;
    
        public int getState() {
            return state;
        }
    
        //主题对象发生变化,通知所有观察者
        public void setState(int state) {
            this.state = state;
            this.notifyAllObserve();
        }
    }
    

    抽象观察者角色:

    public interface Observer {
        void update(Subject sub);
    }
    

    具体观察者角色:

    public class ConcreteObserver implements Observer{
        private int myState;//与目标对象state值保持一致
    
        public void update(Subject sub) {
            myState=((ConcreteSubject)sub).getState();
            System.out.println("观察者得到的值:"+myState);
        }
    }  
    

    客户端 :

    public class Client {
    
        public static void main(String[] args) {
            //目标对象
            ConcreteSubject cs=new ConcreteSubject();
    
            //创建多个具体观察者
            ConcreteObserver observe1=new ConcreteObserver();
            ConcreteObserver observe2=new ConcreteObserver();
            ConcreteObserver observe3=new ConcreteObserver();
    
            //注册观察者
            cs.registerObserve(observe1);
            cs.registerObserve(observe2);
            cs.registerObserve(observe3);
    
            //改变被观察者状态
            cs.setState(2);
    
        }
    }
    

    输出结果
    观察者得到的值:2
    观察者得到的值:2
    观察者得到的值:2
    ###10.3、推模式与拉模式
    推模式:每次都会把通知以广播的方式发送给所有观察者,所有观察者只能被动接收, 推送的信息通常是主题对象的全部或部分数据 。
    拉模式: 主题对象在通知观察者的时候,只传递少量信息。如果观察者需要更具体的信息,由观察者主动到主题对象中获取,相当于是观察者从主题对象中拉数据。一般这种模型的实现中,会把主题对象自身通过update()方法传递给观察者,这样在观察者需要获取数据的时候,就可以通过这个引用来获取了 。
    比较: 推模式是假定主题对象知道观察者需要的数据;而拉模式是主题对象不知道观察者具体需要什么数据,没有办法的情况下,干脆把自身传递给观察者,让观察者自己去按需要取值。
    ###10.4、Java自带对观察者模式的支持
    JavaSE中提供了java.util.Observable和java.util.Observer来实现观察者模式。
    代码示例:
    具体目标对象:

    public class ConcreteSubject extends Observable{
        private int state;
    
        public int getState() {
            return state;
        }
        public void setState(int state) {
            this.state = state;//目标对象状态发生改变
    
            setChanged();//表示目标对象已经做了更改
            notifyObservers(state);//通知所有观察者
        }
    }
    1
    2
    3
    4
    

    具体观察者:

    public class ConcreteObserver implements Observer{
        private int mystate;
        public void update(Observable o, Object arg) {
            mystate=((ConcreteSubject)o).getState();
            System.out.println("观察者接收到的状态:"+mystate);
        }
    }  
    

    客户端:

    public class Client {
        public static void main(String[] args) {
            //创建具体主题
            ConcreteSubject cs=new ConcreteSubject();
    
            //创建观察者
            ConcreteObserver observer1=new ConcreteObserver();
            ConcreteObserver observer2=new ConcreteObserver();
            ConcreteObserver observer3=new ConcreteObserver();
    
            //将观察者加入到目标对象观察者集合
            cs.addObserver(observer1);
            cs.addObserver(observer2);
            cs.addObserver(observer3);
    
            //目标对象改变
            cs.setState(100);
        }
    }
    

    输出结果:
    观察者接收到的状态:100
    观察者接收到的状态:100
    观察者接收到的状态:100


    上面的内容借鉴了很多博主的文章

    展开全文
  • 常用设计模式Python实现

    千次阅读 2019-04-24 13:00:27
    设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 代码查看:...

    设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。

    代码查看:https://github.com/nonpricklycactus/Design-Pattern

    一、设计模式分类

                经典的《设计模式》一书归纳出23种设计模式,这23种模式又可归为,创建型、结构型和行为型3大类

                1.创建型模式分类

                   社会化的分工越来越细,自然在软件设计方面也是如此,因此对象的创建和对象的使用分开也就成为了必然趋势。因为对象的创建会消耗掉系统的很多资源,所以单独对对象的创建进行研究,从而能够高效地创建对象就是创建型模式要探讨的问题。这里有6个具体的创建型模式可供研究,它们分别是:

    • 简单工厂模式(Simple Factory);
    • 工厂方法模式(Factory Method);
    • 抽象工厂模式(Abstract Factory);
    • 创建者模式(Builder);
    • 原型模式(Prototype);
    • 单例模式(Singleton)。

    说明:严格来说,简单工厂模式不是GoF总结出来的23种设计模式之一。

               2. 结构型模式分类

                   在解决了对象的创建问题之后,对象的组成以及对象之间的依赖关系就成了开发人员关注的焦点,因为如何设计对象的结构、继承和依赖关系会影响到后续程序的维护性、代码的健壮性、耦合性等。对象结构的设计很容易体现出设计人员水平的高低,这里有7个具体的结构型模式可供研究,它们分别是:

    • 外观模式(Facade);
    • 适配器模式(Adapter);
    • 代理模式(Proxy);
    • 装饰模式(Decorator);
    • 桥模式(Bridge);
    • 组合模式(Composite);
    • 享元模式(Flyweight

               3. 行为型模式分类

                    在对象的结构和对象的创建问题都解决了之后,就剩下对象的行为问题了,如果对象的行为设计的好,那么对象的行为就会更清晰,它们之间的协作效率就会提高,这里有11个具体的行为型模式可供研究,它们分别是:

    • 模板方法模式(Template Method);
    • 观察者模式(Observer);
    • 状态模式(State);
    • 策略模式(Strategy);
    • 职责链模式(Chain of Responsibility);
    • 命令模式(Command);
    • 访问者模式(Visitor);
    • 调停者模式(Mediator);
    • 备忘录模式(Memento);
    • 迭代器模式(Iterator);
    • 解释器模式(Interpreter)

    二、设计模式的六大原则

               1、开闭原则(Open Close Principle)

                     开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。

               2、里氏代换原则(Liskov Substitution Principle)

                     里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。

                3、依赖倒转原则(Dependence Inversion Principle)

                       这个是开闭原则的基础,具体内容:是对接口编程,依赖于抽象而不依赖于具体。

                 4、接口隔离原则(Interface Segregation Principle)

                        这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。还是一个降低类之间的耦合度的意思,从这儿我们看出,其实设计模式就是一个软件的设计思想,从大型软件架构出发,为了升级和维护方便。所以上文中多次出现:降低依赖,降低耦合。

                  5、迪米特法则(最少知道原则)(Demeter Principle)

                        为什么叫最少知道原则,就是说:一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。

                  6、合成复用原则(Composite Reuse Principle)

                         原则是尽量使用合成/聚合的方式,而不是使用继承。

    三、常用创建型模式实现

               1.工厂模式

                  在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。

    简单工厂模式

                  

                 该例子为通过ShapeFactory对象调用Shape接口实现图形的画出

                 python代码实现:简单工厂.py

    工厂方法模式

                 

                    简单工厂模式只有一个工厂,工厂方法模式对每一个产品都有相应的工厂

                    该例子为通过提供的接口基类创建要调用运算类的运算工厂,对运算类的子类进行调用,通过该方法增加一个运算类(例如N次方类),只需要增加运算类和相对应的工厂,两个类,不需要修改工厂类。

                    python代码实现:工厂模式.py

    抽象工厂模式

                      抽象工厂模式与工厂方法模式的最大区别就在于,工厂方法模式针对的是一个产品等级结构;而抽象工厂模式则需要面对多个产品等级结构。

                      所谓产品族,是指位于不同产品等级结构中,功能相关联的产品组成的家族。比如AMD的主板、芯片组、CPU组成一个家族,Intel的主板、芯片组、CPU组成一个家族。而这两个家族都来自于三个产品等级:主板、芯片组、CPU。一个等级结构是由相同的结构的产品组成,示意图如下:

                   通过使用抽象工厂模式,可以处理具有相同(或者相似)等级结构中的多个产品族中的产品对象的创建问题。如下图所示:

                 由于这两个产品族的等级结构相同,因此使用同一个工厂族也可以处理这两个产品族的创建问题,这就是抽象工厂模式。

                 抽象工厂的功能是为一系列相关对象或相互依赖的对象创建一个接口。一定要注意,这个接口内的方法不是任意堆砌的,而是一系列相关或相互依赖的方法。

    在什么情况下应当使用抽象工厂模式

    •  一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节,这对于所有形态的工厂模式都是重要的。
    •  这个系统的产品有多于一个的产品族,而系统只消费其中某一族的产品。
    •  同属于同一个产品族的产品是在一起使用的,这一约束必须在系统的设计中体现出来。(比如:Intel主板必须使用Intel CPU、Intel芯片组)
    •  系统提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于实现。

               python代码实现:抽象工厂模式.py

          2.建造者模式

              将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

              python代码实现:建造者模式 

        3.单例模式

             保证一个类仅有一个实例,并提供一个访问它的全局访问点。

    #实现__new__方法
    #并在将一个类的实例绑定到类变量_instance上,
    #如果cls._instance为None说明该类还没有实例化过,实例化该类,并返回
    #如果cls._instance不为None,直接返回cls._instance
    class Singleton(object):
        def __new__(cls, *args, **kwargs):
            if not hasattr(cls,'_instance'):
                orig = super(Singleton,cls)
                cls._instance =  orig.__new__(cls)
            return cls._instance
     
    class MyClass(Singleton):
        def __init__(self,name):
            self.name = name
     
     
    a = MyClass("Alex")
    b = MyClass("Jack")
     
     
    print(a.name)
    print(b.name)

     四、常用结构型模式实现

                1.适配器模式

                   将一个类的接口转换成客户希望的另外一个接口。Adapter 模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。 

                   python代码实现:适配器模式.py

                2.桥接模式

                     将抽象部分与实现部分分离,使它们都可以独立的变化。 

                 python代码实现:桥接模式.py

                 深入讲解请参考:http://www.cnblogs.com/houleixx/archive/2008/02/23/1078877.html

               3.组合模式

                  将对象组合成树形结构以表示“部分-整体”的层次结构。C o m p o s i t e 使得用户对单个对象和组合对象的使用具有一致性。 

                  

                    python代码实现:组合模式.py

               4.外观模式

                   为子系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

                    python代码实现:外观模式.py

               5.享元模式

                    运用共享技术有效地支持大量细粒度的对象。

                    python代码实现:享元模式.py

               6.代理模式

                    为其他对象提供一种代理以控制对这个对象的访问。

                    主要解决:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。

                     python代码实现:代理模式.py​​​​​​​

    五、常用行为型模式实现

               1.模板方法模式

                    定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

                    主要解决:一些方法通用,却在每一个子类都重新写了这一方法。

                   python代码实现:模板模式.py​​​​​​​

               2.责任链模式

                     使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。

                    python代码实现:责任链.py​​​​​​​

               3.观察者模式

                   定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时, 所有依赖于它的对象都得到通知并被自动更新。

                    python代码实现:观察者模式.py​​​​​​​

               4.策略模式

                   定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。

                   主要解决:在有多种算法相似的情况下,使用 if...else 所带来的复杂和难以维护。

                     python代码实现:策略模式.py

     

     

     

     

    展开全文
  • 常用设计模式及其 Java 实现

    千次阅读 2018-08-05 14:06:05
    本文按照创建型、结构型、行为型三大类,总结了常见的 24 种设计模式的使用要点,包括适用场景、解决方案、及其相应的 Java 实现。 作者:王克锋 出处:https://kefeng.wang/2018/04/16/design-patterns/ 版权...

    设计模式是在不断出现的特定情境下,针对特定问题,可以重复使用的特定解决模式(套路)。本文按照创建型、结构型、行为型三大类,总结了常见的 24 种设计模式的使用要点,包括适用场景、解决方案、及其相应的 Java 实现。

    作者:王克锋
    出处:https://kefeng.wang/2018/04/16/design-patterns/
    版权:自由转载-非商用-非衍生-保持署名,转载请标明作者和出处。

    1 概述

    1.1 概念

    设计模式,是在某个不断出现的“情境(Context)”下,针对某个“问题”的某种“解决方案”:

    • “问题”必须是重复出现的,“解决方案”必须是可反复应用的;
    • “问题”包含了“一个目标”和“一组约束”,当解决方案在两者之间取得平衡,才是有用的模式;
    • 设计模式不是法律准则,只是指导方针,实际使用时可以根据需要微调,只是要作好注释,以便他人清楚;
    • 很多看似的新模式,实质上是现有模式的变体;
    • 模式的选用原则:尽量用最简单的方式设计,除非为了适应未来确实可能的变化,才采用设计模式,因为设计模式会引入更多类更复杂的关系,不要为了使用模式而使用模式。

    1.2 六大原则



    将六大原则的英文首字母拼在一起就是SOLID(稳定的),所以也称之为 SOLID 原则。

    1.2.1 单一职责原则(Single Responsibility Principle)

    There should never be more than one reason for a class to change.
    一个类只有一个职责,而不是多个职责耦合在一个类中(比如界面与逻辑要分离)。

    1.2.2 开放封闭原则(Open Closed Principle)

    Software entities like classes, modules and functions should be open for extension but closed for modifications.
    对扩展开放,对修改关闭,使用接口和抽象类。

    1.2.3 里氏替换原则(Liskov Substitution Principle)

    Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.
    确保父类可以出现的地方,子类一定可以出现,这是继承复用的基石。

    1.2.4 最少知道原则(Least Knowledge Principle)

    Only talk to you immediate friends.
    低依赖,各实体尽量独立,尽量减少相互作用。

    1.2.5 接口隔离原则(Interface Segregation Principle)

    The dependency of one class to another one should depend on the smallest possible interface.
    客户(client)应该不依赖于它不使用的方法。尽量使用多个接口分工合成,而不是单个接口耦合多种功能。

    1.2.6 依赖倒置原则(Dependency Inversion Principle)

    High level modules should not depends upon low level modules.
    Both should depend upon abstractions.Abstractions should not depend upon details.Details should depend upon abstractions.
    要依赖于抽象(接口或抽象类),而不是具体(具体类)。

    1.3 价值

    设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。
    设计模式看似简单问题复杂化。但“简单”的设计灵活性差,在当前项目中不便扩展,拿到其他项目更是无法使用,相当于“一次性代码”。而设计模式的代码,结构清晰,当前项目中便于扩展,拿到其他项目也适用,是通用的设计。
    很多程序员接触到设计模式之后,都有相见恨晚的感觉,感觉自己脱胎换骨,达到了新的境界,设计模式可以作为程序员划分水平的标准。
    不过我们也不能陷入模式的陷阱,为了使用模式而去套模式,那样会陷入形式主义。

    1.4 选用方法

    • 每个设计模式,都隐含了几个OO原则,当没有合适的设计模式可选时,可回归到OO原则来取舍;
    • 使用模式最好的方式:脑子里装着各种模式,看已有设计或代码中,哪里可以使用这些模式,以复用经验;
    • 共享设计模式词汇(包括口头叫法、代码中类与方法的命名)的威力:
      (1)与他人沟通时,提到设计模式名称,就隐含了其模式;
      (2)使用模式观察软件系统,可以保持在设计层次,而不会被停留在琐碎的对象细节上;
      (3)团队间用设计模式交流,彼此看法不容易误解。

    1.5 重要书籍

    作者:埃里希·伽玛(Erich Gamma), Richard Helm , Ralph Johnson,John Vlissides,后以“四人帮”(Gang of Four,GoF)著称。有两本书:

    1.5.1 《Head First 设计模式》

    强烈推荐阅读。英文书名是《Head First Design Patterns》。
    信耶稣的人都要读圣经,信OO(面向对象)的人都要读四人组的《Head First 设计模式》,官方网站 Head First Design Patterns。2004 该书荣获Jolt奖(类似于电影领域的奥斯卡奖)。

    • 是首次将模式归类的功臣,开启了软件领域的一大跃进;
    • 模式的模板:包括名称、类目、意图、动机、适用性、类图、参与者及其协作、结果、实现、范例代码、已知应用、相关模式等。

    1.5.2 《设计模式:可复用面向对象软件的基础》

    英文书名是《Design Patterns: Elements of Reusable Object-Oriented Software》。也是四人组所著。
    是软件工程领域有关软件设计的一本书,提出和总结了对于一些常见软件设计问题的标准解决方案,称为软件设计模式。这本书在1994年10月21日首次出版,至2012年3月已经印行40刷。

    2 分类与定义

    设计模式可分为三个大类,每个大类又包含若干具体的模式。
    容易混淆的几种模式:简单工厂S / 抽象工厂A / 工厂方法F / 模板方法T

    • “工厂”字样的:带的只用来创建实例,比如 S/A/F;不带的则不限,比如 T;
    • “方法”字样的:带的无需额外的客户端参与,可以独立运转,比如 F/T;不带的需要额外的客户端调用,比如 S/A。

    2.1 创建型(Creational Patterns)

    用于对象的创建,把创建对象的工作放在另一个对象中,或者推迟到子类中。

    2.1.1 单例(Singleton)

    确保一个类只有一个实例,并提供一个全局的访问点。
    需要注意的是,多个类加载器下使用单例,会导致各类加载器下都有一个单例实例,因为每个类加载器都有自己独立的命名空间。
    JDK 中的单例有 Runtime.getRuntime()NumberFormat.getInstance()
    下面总结了四种线程安全的 Java 实现方法。每种实现都可以用 Singleton.getInstance().method(); 调用。

    2.1.1.1 饿汉方式

    关键思路:作为类的静态全局变量,加载该类时实例化。
    缺点是真正使用该实例之前(也有可能一直没用到),就已经实例化,浪费资源。
    对于 Hotspot VM,如果没涉及到该类,实际上是首次调用 getInstance() 时才实例化。

    /**
     * @author: kefeng.wang
     * @date: 2016-06-07 10:21
     **/
    
    public class Singleton {
        private static Singleton instance = new Singleton();
    
        private Singleton() {
        }
    
        // 基于 classLoader 机制,自动达到了线程安全的效果
        public static Singleton getInstance() {
            return instance;
        }
    
        public void method() {
            System.out.println("method() OK.");
        }
    }
    2.1.1.2 懒汉方式

    关键思路:在方法 getInstance() 上实现同步。
    缺点是每次调用 getInstance() 都会加锁,但实际上只有首次实例化时才需要,后续的加锁都是浪费,导致性能大降。

    /**
     * @author: kefeng.wang
     * @date: 2016-06-07 10:21
     **/
    
    public class Singleton {
        private static Singleton instance = null;
    
        private Singleton() {
        }
    
        public static synchronized Singleton getInstance() {
            if (instance == null) {
                instance = new Singleton();
            }
            return instance;
        }
    
        public void method() {
            System.out.println("method() OK.");
        }
    }
    2.1.1.3 懒汉方式(双重检查加锁)

    关键思路:不同步的情况下检查到尚未创建,再同步检查到尚未实例化时,才实例化。以便大大减少同步的情况。
    缺点是:要求 JDK5+,否则许多 JVM 对 volatile 的实现导致双重加锁失效。不过现在极少开发者会用 JDK5,所以该缺点关系不大。

    /**
     * @author: kefeng.wang
     * @date: 2016-06-07 10:21
     **/
    
    public class Singleton {
        private volatile static Singleton instance = null; // 注意 volatile
    
        private Singleton() {
        }
    
        public static Singleton getInstance() {
            if (instance == null) { // 初步检查:尚未实例化
                synchronized (Singleton.class) { // 再次同步(对 Singleton.class)
                    if (instance == null) { // 确认尚未实例化
                        instance = new Singleton();
                    }
                }
            }
    
            return instance;
        }
    
        public void method() {
            System.out.println("method() OK.");
        }
    }
    2.1.1.4 内部静态类方式(推荐!)

    关键思路:全局静态成员放在内部类中,只有该内部类被引用时才实例化,以达到延迟实例化的目的。这是个完美方案:

    • 确保延迟实例化至 getInstance() 的调用;
    • 无需加锁,性能佳;
    • 不受 JDK 版本限制。
    /**
     * @author: kefeng.wang
     * @date: 2016-06-07 10:21
     **/
    
    public class Singleton {
        private static class InstanceHolder { // 延迟加载实例
            private static Singleton instance = new Singleton();
        }
    
        private Singleton() {
        }
    
        public static Singleton getInstance() {
            return InstanceHolder.instance;
        }
    
        public void method() {
            System.out.println("method() OK.");
        }
    }

    2.1.2 生成器(Builder)

    将对象的创建过程,封装到一个生成器对象中,客户按步骤调用它完成创建。
    Java 实现请参考 StringBuilder 的源码,这里给出其使用效果:

    StringBuilder sb = new StringBuilder();
    sb.append("Hello world!").append(123).append('!');
    System.out.println(sb.toString());

    2.1.3 简单工厂(Simple Factory) ★

    不是真正的“设计模式”。自身是工厂实现类,直接提供创建方法(可多个),可以是静态方法。JDK 中有 Boolean.valueOf(String)Class.forName(String)

    /**
     * @author: kefeng.wang
     * @date: 2016-06-09 19:42
     **/
    
    public class DPC3_SimpleFactoryPattern {
        private static class SimpleFactory {
            public CommonProduct createProduct(int type) { // 工厂方法,返回“产品”接口,形参可无
                if (type == 1) {
                    return new CommonProductImplA(); // 产品具体类
                } else if (type == 2) {
                    return new CommonProductImplB();
                } else if (type == 3) {
                    return new CommonProductImplC();
                } else {
                    return null;
                }
            }
        }
    
        private static class SimpleFactoryClient {
            private SimpleFactory factory = null;
    
            public SimpleFactoryClient(SimpleFactory factory) {
                this.factory = factory;
            }
    
            public final void run() {
                CommonProduct commonProduct1 = factory.createProduct(1);
                CommonProduct commonProduct2 = factory.createProduct(2);
                CommonProduct commonProduct3 = factory.createProduct(3);
                System.out.println(commonProduct1 + ", " + commonProduct2 + ", " + commonProduct3);
            }
        }
    
        public static void main(String[] args) {
            SimpleFactory factory = new SimpleFactory(); // 工厂实例
            new SimpleFactoryClient(factory).run(); // 传入客户类
        }
    }

    2.1.4 抽象工厂(Abstract factory) ★

    一个抽象类,定义创建对象的抽象方法。继承后的多个实现类中,实现创建对象的方法。
    客户端灵活选择实现类,完成对象的创建。
    JDK 中采用此模式的有 NumberFormat.getInstance()

    2.1.5 工厂方法(Factory method) ★

    创建方法的对于抽象类和实现类的分工,与“抽象工厂”类似。
    区别在于:本模式无需客户端,自身方法即可完成对象创建前后的操作。

    2.1.6 原型(Prototype)

    当创建实例的过程很复杂或很昂贵时,可通过克隆实现。比如 Java 的 Object.clone()

    2.2 结构型(Structural Patterns)

    用于类或对象的组合关系。

    2.2.1 适配器(Adapter)

    将一个接口适配成期望的另一个接口,可以消除接口不匹配所造成的兼容性问题。
    比如把 Enumeration<E> 适配成 Iterator<E>Arrays.asList()T[] 适配成 List<T>

    2.2.2 桥接(Bridge) ★

    事物由多个因子组合而成,而每个因子都有一个抽象类和多个实现类,最终这多个因子可以自由组合。
    比如多种遥控器+多种电视机、多种车型+多种路况+多种驾驶员。JDK 中的 JDBCAWT

    2.2.3 组合(Composite) ★

    把对象的“部分/整体”以树形结构组织,以便统一对待单个对象或多个对象组合。
    比如多级菜单、二叉树等。

    2.2.4 装饰(Decorator)

    运行时动态地将职责附加到装饰者上。
    扩展功能有两种方式,类继承是编译时静态决定,而装饰者模式是运行时动态决定,有独特优势。
    比如 StringReaderLineNumberReader 装饰后,为字符流扩展出了 line 相关接口。

    2.2.5 外观(Facade) ★

    提供了一个统一的高层接口,用来访问子系统中的一群接口,让子系统更容易使用。
    比如电脑的启动(或关闭),是调用CPU/内存/磁盘各自的启动(或关闭)接口。

    2.2.6 享元 / 蝇量(Flyweight)

    运用共享技术有效地支持大量细粒度的对象。
    比如文本处理器,无需为每个字符的多次出现而生成多个字形对象,而是外部数据结构中同一字符的多次出现共用一个字形对象。
    JDK 中的 Integer.valueOf(int) 就采用此模式。

    2.2.7 代理(Proxy)

    proxy 创建并持有 subject 的引用,client 调用 proxy 时,proxy 会转发给 subject。
    比如 Java 里的 Collections 集合视图、RMI/RPC 远程调用、缓存代理、防火墙代理等。

    2.3 行为型(Behavioral Patterns)

    用于类或对象的调用关系。

    2.3.1 责任链(Chain of responsibility)

    一个请求沿着一条链传递,直到该链上的某个处理者处理它为止。
    比如 SpringMVC 中的过滤器。

    2.3.2 命令(Command)

    将命令封装为对象,可以随意存储/加载、传递、执行/撤消、排队、记录日志等,将“动作的请求者”从“动作的执行者”中解耦。
    参与方包括 Invoker(调用者) => Command(命令) => Receiver(执行者)。
    比如定时任务、线程任务 Runnable

    2.3.3 解释器模式(Interpreter)

    用于创建简易的语言解释器,可处理脚本语言和编程语言,为每个规则创建一个类。
    比如 JDK 中的 java.util.Patternjava.text.Format

    2.3.4 迭代器(Iterator)

    提供一种方法,顺序访问一个聚合对象中的各个元素,而无需暴露其内部表现。
    比如 JDK 中的 java.util.Iteratorjava.util.Enumeration

    2.3.5 中介者(Mediator)

    使用一个中介对象,封装一系列的对象交互,中介对象使各对象无需显式相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
    比如 JDK 中的 java.util.Timerjava.util.concurrent.ExecutorService.submit()

    2.3.6 备忘录(Memento)

    备忘录对象用来存储另一个对象的内部状态的快照,并可在外部存储起来,之后可还原到当初的状态。比如 Java 序列化。
    比如 JDK 中的 java.util.Datejava.io.Serializable

    2.3.7 观察者(Observer)

    对象间一对多的依赖,被观察者状态改变时,观察者都会收到通知。
    参与方包括 Observable(被观察者) / Observer(观察者)。
    比如 RMI 中的事件、java.util.EventListener

    2.3.8 状态(State)

    对象的内部状态变化时,其行为也随之改变。其内部实现是,定义一个状态父类,为每种状态扩展出状态子类,当对象内部状态变化时,所选的状态子类也跟着切换,但外部只需与该对象交互,而不知道状态子类的存在。
    比如视频播放器的停止/播放/暂停等状态。

    2.3.9 策略(Strategy)

    定义一组算法,分别封装起来,独立于客户之外,算法更改时不影响客户使用。
    比如游戏中的不同角色,可以使用各种装备,这些装备可以策略的方式封装起来。
    比如 JDK 中的 java.util.Comparator#compare()

    2.3.10 模板方法(Template method) ★

    抽象类中定义顶级的逻辑框架(叫做“模板方法”),一些步骤(可以创建实例或其他操作)延迟到子类实现,自身可独立运转。
    当子类实现的操作是创建实例时,模板方法就变成了工厂方法模式,所以说工厂方法是特殊的模板方法。

    2.3.11 访问者(Visitor) ★

    在不修改被访问者数据结构的前提下,访问者中封装访问操作,关键点是被访问者中提供被访问的接口。
    适用场景是被访问者稳定但访问者灵活多变,或者访问者有多种不同类的操作。

    2.4 复合模式(Compound)

    结合两个或更多模式,组成一个解决方案,解决经常发生的一般性问题。
    使用案例:MVC模式(Model/View/Controller),使用了观察者(Observer)、策略(Strategy)、组合(Composite)、工厂(Factory)、装饰器(Decorator)等模式。
    使用案例:家用电器=界面+数据+逻辑控制,商场=店面+仓库+逻辑控制。

    3 参考文档

    维基百科: 设计模式
    Wikipedia: Software design pattern
    TutorialsPoint: Design Pattern

    展开全文
  • 用Python实现9种常用设计模式

    千次阅读 2019-06-27 18:45:48
    这里总结9种常见的设计模式作为参考,并用Python实现设计模式分类 点击链接跳转: 创建型设计模式 单例模式 工厂模式 结构型设计模式 门面模式 代理模式 行为型设计模式 命令模式 模板方法模式 观察者...

    设计模式

    设计模式是一套被反复使用的代码结构,是代码设计经验的总结。他提高了代码的可重用性,让代码更容易被他人理解,保证代码可靠性。
    这里总结9种常见的设计模式作为参考,并用Python实现。

    设计模式分类

    点击链接跳转:

    展开全文
  • java常用设计模式详解应用

    千次阅读 2019-01-18 10:10:31
    java常用设计模式详解应用 java有23种设计模式,用于解决在编程当中遇到的实际问题。本文主要讲解单例模式、工厂模式、适配器模式、装饰模式、代理模式、模板模式、策略模式、观察者模式。 ...
  • 常用设计模式

    千次阅读 2020-06-29 21:41:01
    设计模式 从程序的结构上实现松耦合,从而可以扩大整体的类结构,用来解决更大的问题。 设计模式的本质是面向对象设计原则的实际运用,是对类的封装性、继承性和多态性以及类的关联和组合关系的充分理解。 正确使用...
  • 常用设计模式-策略模式

    万次阅读 2020-11-20 09:51:54
    模式实现 环境类(Context)用一个ConcreteStrategy对象来配置。维护一个对Strategy对象的引用。可定义一个接口来让Strategy访问它的数据。 抽象策略类(Strategy)定义所有支持的算法的公共接口。 Context使用这个...
  • Java常用设计模式总结应用场景分析

    万次阅读 多人点赞 2019-03-12 16:24:00
    原 java常用设计模式总结 2017年11月23日 21:30:54 qq_14827935 阅读数:3284 ...
  • 常用设计模式-适配器模式

    万次阅读 2020-11-27 13:15:05
    这种类型的设计模式属于结构型模式。 优点:1、可以让任何两个没有关联的类一起运行。 2、提高了类的复用。 3、增加了类的透明度。 4、灵活性好。 缺点:1、过多地使用适配器,会让系统非常零乱,不易整体进行把握...
  • 常用设计模式-原型模式

    万次阅读 2020-11-27 10:25:17
    模式简介 用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象 ...模式实现 1、实现克隆操作,在 JAVA 继承 Cloneable,重写 clone(),在 .NET 中可以使用 Object 类的 MemberwiseClone() 方
  • C++各类设计模式及实现详解

    万次阅读 多人点赞 2017-08-19 10:18:46
    总结了网上一些设计模式资料。  软件领域中的设计模式为开发人员提供了一种使用专家设计经验的有效途径。设计模式中运用了面向对象编程语言的重要特性:封装、继承、多态,真正领悟设计模式的精髓是可能一个漫长的...
  • 常用设计模式-模板方法模式

    万次阅读 2020-11-23 11:19:20
    模式简介 模板方法(Template Method)模式的定义如下:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。它是一种类行为型模式。 该...
  • Android中常用设计模式

    千次阅读 2018-04-07 10:27:25
    Java 中一般认为有 23 种设计模式,我们不需要所有的都会,但是其中常用的几种设计模式应该去掌握。下面列出了所有的设计模式。需要掌握的设计模式我单独列出来了,当然能掌握的越多越好。 总体来说设计模式分为三...
  • 常用设计模式(面试)

    万次阅读 多人点赞 2018-01-01 17:34:34
    Singleton(单例模式) 一句话总结:一个类在Java虚拟机中只有一个对象,并提供一个全局访问点。 生活中例子:太阳、月亮、国家主席等。 解决什么问题:对象的唯一性,性能浪费太多。 项目里面怎么用:...
  • 常用设计模式---组合模式C++实现
  • 常用设计模式---命令模式(C++实现
  • 常用设计模式-工厂方法

    万次阅读 2020-11-19 14:12:05
    模式简介 工厂模式主要是为创建对象提供了接口。工厂模式按照《Java与模式》中的提法分为三类: 1. 简单工厂模式(Simple Factory) 2. 工厂方法模式(Factory Method) 3. 抽象工厂模式(Abstract Factory) 工厂方法...
  • 常用设计模式---观察者模式(C++实现

    千次阅读 多人点赞 2014-01-08 17:55:01
    常用设计模式---装饰者模式(C++实现
  • 常用设计模式---适配器模式(C++实现
  • java常用设计模式总结

    万次阅读 多人点赞 2017-11-23 21:30:54
    掌握常用的几种(最起码单例模式、工厂模式),了解其他的设计模式即可,做到手里有粮,心里不慌。首先,掌握每种模式的定义使用场景。其次,掌握一个形象的例子,简单的过一遍代码。 学习设计模式的真正目的:...
  • C++简单实现几种常用设计模式

    千次阅读 多人点赞 2017-05-07 22:03:31
    本文介绍几种常用设计模式并给出C++实现。 1.单例模式 作用:保证一个类只有一个实例,并提供一个访问它的全局访问点,使得系统中只有唯一的一个对象实例。 应用:常用于管理资源,如日志、线程池 实现要点: 在...
  • 常用设计模式-简单工厂

    万次阅读 2019-05-13 10:34:59
    模式简介 在三种工厂模式中,简单工厂模式是最基础的一个。该模式的核心思想是把创建对象过程和对象本身业务处理分离。假设需要创建多个对象,每个对象的创建模式比较复杂,那么我们把这些过程放在逻辑代码中,会...
  • C++ 常用设计模式

    千次阅读 2019-06-17 20:51:46
    工厂模式 策略模式 适配器模式 单例模式 原型模式 模板模式 建造者模式 外观模式 组合模式 代理模式 享元模式 桥接模式 装饰模式 备忘录模式 中介者模式 职责链模式 观察者模式 工厂模式 工厂模式:简单工厂模式、...
  • PHP常用设计模式

    千次阅读 2019-04-01 10:25:32
    一、工厂模式 通过工厂类的方法返回对象,而不是用的时候在代码中直接new对象。 对比 代码直接new:某一个类或者方法改变之后,所有用到的地方都要修改 使用工厂类:只需要改变相应的工厂类 <?php class Test1{ ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 427,411
精华内容 170,964
关键字:

常用设计模式及实现