• Java常用的几种设计模式


      本来想写点spring相关的东西的,想来想去,先写点设计模式的东西吧

      什么是设计模式?套用百度百科的话解释吧

      设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。

      这么说,新人应该很难捉摸,大体的说,设计模式就是设计代码结构,方便开发或者以后的调整,学习java,应该没有不会常用设计模式的吧?

      设计模式遵循开闭原则,对拓展开放,对修改关闭,就是说,我们一个功能写好使用之后,如果要为这个功能进行拓展,那尽量不要去修改它的代码,而采用一些其他的方式去实现它,而这些其他的方式,就是设计模式。

      java常用的设计模式大致分为3类总共有二三十种吧,但是真正进行应用开发使用的不多,如果是做框架方面的,可以多了解一下,肯定是有帮助的,所以这里就只介绍应用开发常用的八种设计模式

    一、工厂模式

      工厂模式应该都很熟悉,就是创建对象用的,看下面的例子:

    package com.example;
    
    public interface Mobile {
        public void play();
        public void call();
    }
    package com.example;
    
    public class Android implements Mobile {
    
        public void play() {
            // TODO Auto-generated method stub
            System.out.println("Android play");
        }
    
        public void call() {
            // TODO Auto-generated method stub
            System.out.println("Android call");
        }
    }

      首先,我们有个手机的接口Mobile,手机可以打电话,可以玩游戏,接着有个Android实现了手机接口,它实现了打电话玩游戏的功能

    package com.example;
    
    public class MobileFactory {
        public static Mobile create() {
            return new Android();
        }
    }

      接着,我们创建一个工厂,用工厂创建Android手机类打电话,玩游戏,测试一下

    package com.example;
    
    public class Test {
    
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            Mobile mobile = MobileFactory.create();
            mobile.call();// 打电话
            mobile.play();// 玩游戏
        }
    
    }

      结果输出:

      

      如果哪天业务需求变了,不使用Android手机了,要使用IPhone手机,那么我们只需用IPhone类实现Mobile接口,然后修改工厂的创建功能即可

    package com.example;
    
    public class IPhone implements Mobile {
    
        public void play() {
            // TODO Auto-generated method stub
            System.out.println("IPhone call");
        }
    
        public void call() {
            // TODO Auto-generated method stub
            System.out.println("IPhone call");
        }
    
    }
    package com.example;
    
    public class MobileFactory {
        public static Mobile create() {
            return new IPhone();
        }
    }

      然后同样使用上面的测试得到结果

      

      使用工厂模式就是提醒我们,写代码的时候,尽可能的避免使用new关键字创建对象,因为new关键字意味着依赖,如果一个类到处都是使用new关键字实例化,那么哪天这个类不用了,要换成其它的实现类,我们就需要到处修改,这样就太消耗时间了

      再者,我们上面的列子中,工厂MobileFactory中的创建方法是静态方法,当然我们也可以换成一般的对象方法,只不过我们调用时需要创建对象去调用方法,但是可以在实例化工厂对象的时候往构造函数中传入一些参数,从而可以对创建的对象进行一些配置或者改造,另外,根据面向接口开发的思想,我们也可以为这个工厂创建一个接口,需要实例化的工厂只需实现接口的就可以了

      还有,工厂可以有多个创建方法,创建方法可以带参数,如上面的例子,如果需求中,我们Android类和IPhone都需要使用到,我们可以在create方法中传入一个参数,用于指定需要实例化的是Android类还是IPhone类,或者直接创建两个方法,一个用于实例化Android类,一个实例化IPhone类,如:  

    package com.example;
    
    public class MobileFactory {
        public static Mobile create(int flag) {
            if (flag == 1) {
                return new Android();
            } else {
                return new IPhone();
            }
        }
    }

      或者

    package com.example;
    
    public class MobileFactory {
        public static Mobile createAndroid() {
            return new Android();
        }
    
        public static Mobile createIPhone() {
            return new IPhone();
        }
    }

      总之,工厂模式就是提醒我,当需要实例化对象时,我们尽可能避免使用new去实例化,考虑为对象创建一个统一的来源

     二、单例模式

      单例模式,表面上很容易理解,就是说这个类型在存在期间只有一个实例,实现方法就是讲构造函数私有化,从而除了类内部,其它地方均不能实例化类的对象,如:

    package com.example;
    
    public class Context {
        private static Context instance = null;
    
        private Context() {
        }
    
        public synchronized static Context getInstance() {
            if (instance == null) {
                instance = new Context();
            }
            return instance;
        }
    }

      上面的Context类的构造函数被私有化了,因此外面是不能实例化它的,但是为了提供Context类的实例,这里就有了一个getInstance静态方法,外面测试一下:

    package com.example;
    
    public class Test {
    
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            Context ctx1=Context.getInstance();
            Context ctx2=Context.getInstance();
            System.out.println(ctx1.equals(ctx2));
        }
    
    }

      执行结果:

      

      可以看到,上面实例化得到的是同一个对象,细心的朋友可能会看到,在getInstance方法中,外面使用synchronized关键字,这个关键字的作用可以理解为上锁,当一个线程进入方法时,其它线程就需要等待,当然,我们上面的例子里面,如果去掉这个关键字,也是不会报错的,甚至在开发时写单例,发布生产环境也可能不报错,但总归不是线程安全的,可能在一个线程去实例化这个唯一对象时,属性等等还没设置好,另外一个线程又去获取了这个对象,然后获取属性,发现为空,可能就会引发异常

      另外,因为synchronized关键字是修饰方法的,也就是说每次只有一个线程能进这个方法里面去,哪怕是这个唯一实例生成后,这样不免造成一些性能上不好的影响,所以我们可以这样去实现:

    package com.example;
    
    public class Context {
        private static Context instance = null;private Context() {
        }
        
        private synchronized static void init() {
            if (instance == null) {
                instance = new Context();
            }
        }
    
        public static Context getInstance() {
            if (instance == null) {
                init();
            }
            return instance;
        }
    }

      上面的例子中,synchronized还是修饰方法,但是已经不是对外的方法了,所以在某种程度上说,会更好一些。

    三、适配器模式

       适配器主要指的是对接口方法进行适配,看下面的例子

    package com.example;
    
    public interface Phone {
        public void time();
        public void call() ;
    }
    package com.example;
    
    public class Watch {
        public void time() {
            System.out.println("I am Watch:time");
        }
    }

      首先,我们有一个电话接口,电话可以看时间,打电话,同时我们有一个手表类,手表只能看时间,如果我们现在要写一个类去实现电话接口,一般需要实现它里面的所有方法,但是我们已经有了Watch类,所以我们可以将Watch类用上,具体实现如下:   

    package com.example;
    
    public class Telephone extends Watch implements Phone {
    
        public void call() {
            // TODO Auto-generated method stub
            System.out.println("I am Telephone:call");
        }
    }

      测试一下调用:

    package com.example;
    
    public class Test {
    
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            Phone phone=new Telephone();
            phone.time();
            phone.call();
        }
    
    }

      结果:  

      

      可以看到,这里我们只实现了Phone的call接口,但是Telephone继承了Watch类,所以它有time方法,而且正好和Phone的time方法是同名同参数的,所以自动适配到了Phone接口的time方法,但是现实开发中,我们往往会碰到方法名或者参数不一样,但是同样功能的方法,我们就只能去显示的实现Phone的time接口了:

    package com.example;
    
    public class Telephone implements Phone {
    
        public void call() {
            // TODO Auto-generated method stub
            System.out.println("I am Telephone:call");
        }
    
        public void time() {
            // TODO Auto-generated method stub
            new Watch().time();
        }
    }

      另外,如果我们不想让Telephone有父类,或者Telephone有其他的父类要继承,因为java的单继承远程,我们也可以这么使用

      适配器模式可以理解为用已有的方法去实现同功能的接口,从而不至于造成同样的功能几个地方都有。还有,真实开发过程中,我们可以实现类和接口之间添加一些父类,这样可以更利于我们封装一些同功能的方法

    四、策略模式

      程序里,我们经常会看到像下面  

         int result=0;
            if(flag==1){
                //做一些计算得到result
            }else if(flag==2){
                //做另外一些计算得到result
            }else{
                //做另外一些计算得到result
            }

      如果这里面每个flag要做一些动作,那么这样的代码很不容易理解,而且也不方便拓展。于是我们可以创建一个这些操作的接口,然后每个flag对应的操作封装到不同实现这个接口的类型,比如:

    package com.example;
    
    public interface Calculator {
        public int exec(int a,int b);
    }
    package com.example;
    
    public class PlusCalculator implements Calculator {
    
        public int exec(int a, int b) {
            // TODO Auto-generated method stub
            return a + b;
        }
    
    }
    package com.example;
    
    public class MinusCalculator implements Calculator {
    
        public int exec(int a, int b) {
            // TODO Auto-generated method stub
            return a - b;
        }
    
    }

      测试使用:

    package com.example;
    
    public class Test {
    
        public static void main(String[] args) {
            // TODO Auto-generated method stub
    
            int flag = 0;
            Calculator calculator = null;
            if (flag == 1) {
                calculator = new PlusCalculator();
            } else {
                calculator = new MinusCalculator();
            }
            int result = calculator.exec(5, 3);
            System.out.println(result);
        }
    
    }

      这里演示的只是简单的加减,但是实际的业务算法一般是比较复杂,代码量比较多的,如果不封装,代码会难看

      当然,我们也可以为这些实现类写个父类,因为一般的,这些实现类都会有一些公共的逻辑  

    五、装饰模式

      装饰模式也有人叫修饰模式,其实,装饰模式就是对一些操作增加一些功能,比如在操作之前记录操作日志,操作之后记录结果,

      比如,上面我们定义了一个计算接口,并有两个类实现了接口,加法类PlusCalculator 和减法类MinusCalculator ,正常调用,我们只有结果,到底哪两个参数我们不知道:

    package com.example;
    
    public class Test {
    
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            Calculator plus = new AddCalculator();
            int plusResult = plus.exec(5, 3);
            System.out.println(plusResult);
    
            Calculator minus = new MinusCalculator();
            int minusResult = minus.exec(5, 3);
            System.out.println(minusResult);
        }
    
    }

      

      现在我们可以创建装饰类,用来记录打印计算的参数:

    package com.example;
    
    public class Decorator implements Calculator {
        Calculator calculator = null;
    
        public Decorator(Calculator calculator) {
            this.calculator = calculator;
        }
    
        public int exec(int a, int b) {
            // TODO Auto-generated method stub
    
            System.out.println("before:a=" + a + "    b=" + b);
            int result = calculator.exec(a, b);
            System.out.println("after:save result:" + result);
            return result;
        }
    
    }

      我们把测试改一下,执行后得到结果:

    package com.example;
    
    public class Test {
    
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            Calculator plus = new Decorator(new PlusCalculator());
            int plusResult = plus.exec(5, 3);
            System.out.println(plusResult);
    
            Calculator minus = new Decorator(new MinusCalculator());
            int minusResult = minus.exec(5, 3);
            System.out.println(minusResult);
        }
    
    }

      

      这样,我们就记录到了参数信息,这种做法可以用法记录操作日志和结果,简单的说,装饰模式就是拓展一个类型的功能,而不是去方法调用处一个个去加,如果接口的某些方法不想用了,可以抛出提示

    六、代理模式

      代理模式在形式上和装饰模式有点像,但是和装饰模式的功能不一样,装饰模式更像修饰,修饰一个 接口,然后实现接口的所有类都能被修饰到,所以装饰的对象一般都是接口,而代理模式充当的是中介者的角色,你要访问必须通过类A去访问类B,所以代理的对象一般都是真实的类对象,比如上面的加法类和减法类,我们可以创建他们的代理:  

    package com.example;
    
    public class PlusProxy implements Calculator {
        PlusCalculator calculator;
    
        public PlusProxy() {
            calculator = new PlusCalculator();
        }
    
        public int exec(int a, int b) {
            // TODO Auto-generated method stub
            System.out.println("before:a=" + a + "  b=" + b + "    a+b=?");
            int result = calculator.exec(a, b);
            System.out.println("after:a+b=" + result);
            return result;
        }
    
    }
    package com.example;
    
    public class MinusProxy implements Calculator {
    
        MinusCalculator calculator;
    
        public MinusProxy() {
            calculator = new MinusCalculator();
        }
    
        public int exec(int a, int b) {
            // TODO Auto-generated method stub
            System.out.println("before:a=" + a + "  b=" + b + "    a-b=?");
            int result = calculator.exec(a, b);
            System.out.println("after:a-b=" + result);
            return result;
        }
    
    }

      然后我们使用代理类去操作,而不是直接对用对应的计算类操作:

    package com.example;
    
    public class Test {
    
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            Calculator plus = new PlusProxy();
            int plusResult = plus.exec(5, 3);
            System.out.println(plusResult);
    
            Calculator minus = new MinusProxy();
            int minusResult = minus.exec(5, 3);
            System.out.println(minusResult);
        }
    
    }

      结果输出:

      

      可以这么说,装饰模式和代理模式差不多,只是他们作用对象的区别

    七、模板设计模式

      这个设计模式可以这么理解,把通用性的东西设置好,关键的部分放空,或者设置一个默认操作,然后使用模板是,只需要重写关键部分就可以了,换成类的方式去说就是,使用一个父类设计好模板,放出一些方法供子类实现或重写,比如:

    package com.example;
    
    public abstract class People {
        protected abstract String getName();
    
        protected abstract String getWord();
    
        public void say() {
            System.out.println(getName() + ":" + getWord());
        }
    }

      我们有一个People类,它有三个方法,但是有两个方法没有具体实现,而是留给子类去实现,我们写两个类去实现它  

    package com.example;
    
    public class Teacher extends People {
    
        @Override
        protected String getName() {
            // TODO Auto-generated method stub
            return "老师";
        }
    
        @Override
        protected String getWord() {
            // TODO Auto-generated method stub
            return "上课";
        }
    
    }
    package com.example;
    
    public class Student extends People {
    
        @Override
        protected String getName() {
            // TODO Auto-generated method stub
            return "学生";
        }
    
        @Override
        protected String getWord() {
            // TODO Auto-generated method stub
            return "老师好";
        }
    
    }

      我们测试调用:

    package com.example;
    
    public class Test {
    
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            People teacher=new Teacher();
            teacher.say();
            
            People student=new Student();
            student.say();
    
        }
    
    }

      

    八、观察者模式

      观察者模式有点像回调函数,有点像订阅,就是先订阅,当状态发生变化时再通知,比如:

    package com.example;
    
    public interface Observer {
        public void send();
    }

      首先,我们有一个观察者接口,它有一个通知方法,就是通知订阅对象,QQ用户和微信用户都可以实现这个接口

    package com.example;
    
    public class QQObserver implements Observer {
    
        public void send() {
            // TODO Auto-generated method stub
            System.out.println("QQ received");
        }
    
    }
    package com.example;
    
    public class WxObserver implements Observer {
    
        public void send() {
            // TODO Auto-generated method stub
            System.out.println("Wx received");
        }
    
    }

      现在,观察者有两种类型,QQ用户和微信用户,然后我们可以定义一个博客类

    package com.example;
    
    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.List;
    
    public class Blog {
        List<Observer> list=new ArrayList<Observer>();
        
        public void add(Observer obs) {
            list.add(obs);
        }    
        
        public void publish() {
            System.out.println("new blog published");
            
            Iterator<Observer> ite= list.iterator();
            while (ite.hasNext()) {
                Observer obs=ite.next();
                obs.send();
            }
        }
    }

      博客可以通过add方法添加订阅对象,当博客发布后,就会去通知这些订阅的对象,我们测试一下:

    package com.example;
    
    public class Test {
    
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            Observer qq=new QQObserver();
            Observer wx=new WxObserver();
            
            Blog blog=new Blog();
            blog.add(qq);//qq用户订阅
            blog.add(wx);//微信用户订阅
            blog.publish();//博客发布        
        }
    }

      

      

      总结:设计模式有很多种,但都是一些理解性的东西,自己动手敲敲代码能更好的理解,每种设计模式都特定的用处,要看具体情况具体看。不要认为设计模式很麻烦就不注重它,无数的经验告诉我们,一个好的程序设计能节省我们大量的时间和精力

      

  • 相关阅读:
    赋值运算 就是 +*/ 什么的啦
    利用application,cookies,sessino以及文件文件操作制作计数器和投票的综合实例(按学习进程更新)
    实现二级联动菜单 C#,带完整数据库 SQL
    写入、读取 cookie 无聊顺便复习了下前面学的东西!
    两级联动菜单之ListBox.item.add做法
    利用textbox接收两个数,列出一个数组,并做简单的运算
    .net服务器控件列表19个
    循环显示checkboxlist控件项的时候,count到底要不要 1
    validation验证控件案例以及正则表达式中几个特殊符号的说明!
    上传文件 动作详解(最简单的这种)
  • 原文地址:https://www.cnblogs.com/shanfeng1000/p/10912155.html
Copyright © 2020-2023  润新知