设计模式1(创建型模式)

面向对象

当我们习惯了面向过程编程时,发现在程序过程中到处找不到需要面向对象的地方,最主要的原因,是思维没有转变。程序员通常在拿到一个需求的时候,第一个反应就是如何实现这个需求,这是典型的面向过程的思维过程,而且很快可能就实现了它。而面向对象,面对的却是客体,第一步不是考虑如何实现需求,而是进行需求分析,就是根据需求找到其中的客体,再找到这些客体之间的联系。因此面向过程和面向对象的思维转变的关键点,就是在第一步设计,拿到需求后,一定先不要考虑如何实现它,而是通过UML建模,然后按照UML模型去实现它。这种思路的转变,可能需要个过程

六原则一法则

- 单一职责原则:一个类只做它该做的事情。(单一职责原则想表达的就是”高内聚”,写代码最终极的原则只有六个字”高内聚、低耦合”,就如同葵花宝典或辟邪剑谱的中心思想就八个字”欲练此功必先自宫”,所谓的高内聚就是一个代码模块只完成一项功能,在面向对象中,如果只让一个类完成它该做的事,而不涉及与它无关的领域就是践行了高内聚的原则,这个类就只有单一职责。我们都知道一句话叫”因为专注,所以专业”,一个对象如果承担太多的职责,那么注定它什么都做不好。这个世界上任何好的东西都有两个特征,一个是功能单一,好的相机绝对不是电视购物里面卖的那种一个机器有一百多种功能的,它基本上只能照相;另一个是模块化,好的自行车是组装车,从减震叉、刹车到变速器,所有的部件都是可以拆卸和重新组装的,好的乒乓球拍也不是成品拍,一定是底板和胶皮可以拆分和自行组装的,一个好的软件系统,它里面的每个功能模块也应该是可以轻易的拿到其他系统中使用的,这样才能实现软件复用的目标。)
职责越单一影响到外层模块的可能性就越小,这样出错的概率也就越低。

- 开闭原则:软件实体应当对扩展开放,对修改关闭。(在理想的状态下,当我们需要为一个软件系统增加新功能时,只需要从原来的系统派生出一些新类就可以,不需要修改原来的任何一行代码。要做到开闭有两个要点:①抽象是关键,一个系统中如果没有抽象类或接口系统就没有扩展点;②封装可变性,将系统中的各种可变因素封装到一个继承结构中,如果多个可变因素混杂在一起,系统将变得复杂而换乱,如果不清楚如何封装可变性,可以参考《设计模式精解》一书中对桥梁模式的讲解的章节。)

- 依赖倒转原则:面向接口编程。(该原则说得直白和具体一些就是声明方法的参数类型、方法的返回类型、变量的引用类型时,尽可能使用抽象类型而不用具体类型,因为抽象类型可以被它的任何一个子类型所替代,请参考下面的里氏替换原则。)

- 里氏替换原则:任何时候都可以用子类型替换掉父类型。(关于里氏替换原则的描述,Barbara Liskov女士的描述比这个要复杂得多,但简单的说就是能用父类型的地方就一定能使用子类型。里氏替换原则可以检查继承关系是否合理,如果一个继承关系违背了里氏替换原则,那么这个继承关系一定是错误的,需要对代码进行重构。例如让猫继承狗,或者狗继承猫,又或者让正方形继承长方形都是错误的继承关系,因为你很容易找到违反里氏替换原则的场景。需要注意的是:子类一定是增加父类的能力而不是减少父类的能力,因为子类比父类的能力更多,把能力多的对象当成能力少的对象来用当然没有任何问题。)

- 接口隔离原则:接口要小而专,绝不能大而全。(臃肿的接口是对接口的污染,既然接口表示能力,那么一个接口只应该描述一种能力,接口也应该是高度内聚的。例如,琴棋书画就应该分别设计为四个接口,而不应设计成一个接口中的四个方法,因为如果设计成一个接口中的四个方法,那么这个接口很难用,毕竟琴棋书画四样都精通的人还是少数,而如果设计成四个接口,会几项就实现几个接口,这样的话每个接口被复用的可能性是很高的。Java中的接口代表能力、代表约定、代表角色,能否正确的使用接口一定是编程水平高低的重要标识。)

- 合成聚合复用原则:优先使用聚合或合成关系复用代码。

通过继承来复用代码是面向对象程序设计中被滥用得最多的东西,因为所有的教科书都无一例外的对继承进行了鼓吹从而误导了初学者,类与类之间简单的说有三种关系,Is-A关系、Has-A关系、Use-A关系,分别代表继承、关联和依赖。其中,关联关系根据其关联的强度又可以进一步划分为关联、聚合和合成,但说白了都是Has-A关系,合成聚合复用原则想表达的是优先考虑Has-A关系而不是Is-A关系复用代码,因为继承将父类的实现暴漏给子类了,父类发生该改变,子类也不得不发生改变。

- 迪米特法则:迪米特法则又叫最少知识原则,一个对象应当对其他对象有尽可能少的了解。(迪米特法则简单的说就是如何做到”低耦合”,门面模式和调停者模式就是对迪米特法则的践行。对于门面模式可以举一个简单的例子,你去一家公司洽谈业务,你不需要了解这个公司内部是如何运作的,你甚至可以对这个公司一无所知,去的时候只需要找到公司入口处的前台美女,告诉她们你要做什么,她们会找到合适的人跟你接洽,前台的美女就是公司这个系统的门面。再复杂的系统都可以为用户提供一个简单的门面,Java Web开发中作为前端控制器的Servlet或Filter不就是一个门面吗,浏览器对服务器的运作方式一无所知,但是通过前端控制器就能够根据你的请求得到相应的服务。调停者模式也可以举一个简单的例子来说明,例如一台计算机,CPU、内存、硬盘、显卡、声卡各种设备需要相互配合才能很好的工作,但是如果这些东西都直接连接到一起,计算机的布线将异常复杂,在这种情况下,主板作为一个调停者的身份出现,它将各个设备连接在一起而不需要每个设备之间直接交换数据,这样就减小了系统的耦合度和复杂度,如下图所示。迪米特法则用通俗的话来将就是不要和陌生人打交道,如果真的需要,找一个自己的朋友,让他替你和陌生人打交道。)
unknown_filename.1|379

unknown_filename|379

综述

简述一下你了解的设计模式。

  • 所谓设计模式,就是一套被反复使用的代码设计经验的总结(情境中一个问题经过证实的一个解决方案)。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。设计模式使人们可以更加简单方便的复用成功的设计和体系结构。将已证实的技术表述成设计模式也会使新系统开发者更加容易理解其设计思路。
  • 被问到关于设计模式的知识时,可以拣最常用的作答,例如: Builder(建造者模式),Factory Method(工厂方法模式),Abstract Factory(抽象工厂模式),Singleton(单例模式);Facade(外观模式),Strategy(策略模式),Template Method(模板方法模式), 委托模式、Proxy(代理模式)、Adapter(适配器模式),Observer(观察者模式),Chain Of Responsibility(责任链模式),Decorator(装饰模式)

项目中用到哪些模式

  • 单例、建造者、外观、责任链(桥统一)、策略(桥重构)、模板方法
  • 首先这个业务,使用责任链模式,肯定是不合适的,因为弹窗之间的耦合性很低,并没有什么明确的上下游关系
  • 做之前先想想技术方案、以后的扩展,多思考
    [[桥统一重构技术文档]]

设计模式运用好的文章:应用策略和责任链
https://juejin.cn/post/6999875193082478600?utm_source=gold_browser_extension
https://juejin.cn/post/6999914854618234888?utm_source=gold_browser_extension

在拿到原始需求时,不要上来就肝代码,而是 对需求进行拆解、分析、假设和思考

哪些源码用到了设计模式

责任链模式:try-catch、有序广播、viewgroup事件传递;
建造者模式:AlertDialog
装饰模式:Collections工具类、I/O、context;
观察者模式:android中的回调模式、listview的notifyDataChanged、rxjava;
外观模式:context;
模板方法模式:AsnycTask、Activity;
策略:Volley、动画中的时间插值器,recycleview中不同的apdater

分类

创建型:怎么创建对象
结构型:描述如何将类或对象结合在一起形成更大的结构
行为型:对在不同的对象之间划分责任和算法的抽象化

unknown_filename.6

创建型模型

这类模式提供创建对象的机制,能够提升已有代码的灵活性和可复用性。

单例

保证一个类在内存中的对象唯一性。
1,不允许其他程序用new创建该类对象。
2,在该类创建一个本类实例。
3,对外提供一个方法让其他程序可以获取该对象。
好处:
对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销
由于new操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻GC压力,缩短GC停顿时间

饿汉(直接就创建)

1
2
3
4
5
6
7
8
9
10
public class HungurySingleton {
    private static final HungurySingleton mHungurySingleton = new HungurySingleton();
    private HungurySingleton(){
        System.out.println("Singleton is create");

    }
    public static HungurySingleton getHungurySingleton() {
        return mHungurySingleton;
    }
}

懒汉(一开始赖的创建)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class LazySingleton {
    private static LazySingleton instance;
    private LazySingleton() {
    }
    public static LazySingleton getInstance() {
        // 第一次调用的时候会被初始化
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}


懒汉安全

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class LazySafetySingleton {
    private static LazySafetySingleton instance;  
    private LazySafetySingleton (){
    }

   //方法中声明synchronized关键字
   public static synchronized LazySafetySingleton getInstance() {
     if (instance == null) {  
         instance = new LazySafetySingleton();  
     }  
     return instance;  
   }

   //同步代码块实现
}

DCL

假设没有关键字 volatile 的情况下,两个线程 A、B,都是第一次调用该单例方法,线程 A 先执行 instance = new Instance(),该构造方法编译后生成多条字节码指令,由于 JAVA 的指令重排序,可能会先执行 instance 的赋值操作,该操作实际只是在内存中开辟一片存储对象的区域后直接返回内存的引用,之后 instance 便不为空了,但是实际的初始化操作却还没有执行,如果就在此时线程 B 进入,就会看到一个不为空的但是不完整(没有完成初始化)的 Instance 对象,所以需要加入 volatile 关键字,禁止指令重排序优化,从而安全的实现单例。

加入同步为了解决多线程安全问题。加入双重判断是为了解决效率问题。

不写if语句的话,每次都会判断同步锁,这样写的话只在第一次判断并创建对象。以后在不会执行if里的代码了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class DclSingleton {
    private static volatile DclSingleton mInstance = null;
//  private static DclSingleton mInstance = null;

    private DclSingleton() {
    }

    public static DclSingleton getInstance() {
        // 避免不必要的同步
        if (mInstance == null) {
            // 同步
            synchronized (DclSingleton.class) {
                // 在第一次调用时初始化
                if (mInstance == null) {
                    mInstance = new DclSingleton();
                }
            }
        }
        return mInstance;
    }
}

静态内部类单例

(推荐)
静态内部类的优点是:外部类加载时并不需要立即加载内部类,内部类不被加载则不去初始化 INSTANCE,故而不占内存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    public class Singleton{
        private Singleton(){
        }
        public static Singleton getInstance(){
            // 6行
            return SingletonHelper.instance;
        }

        // 9行
        private static class SingletonHelper{
            private final static Singleton instance = new Singleton();
        }

}

看这段代码 9 ~ 11 行,静态内部类 SingletonHelper 中的静态属性 instance 是随着 SingtonHelper 的加载而加载的,也就是说一旦静态内部类 SingletonHelper 加载,就会对 Singleton 类进行实例化;

由于静态内部类只会被加载一次,因此在多线程环境下也可以保证单例对象的线程安全。
具体来说,假设有两个线程 A 和 B,它们同时调用 Singleton.getInstance() 方法,由于内部类 InnerHolder 尚未被加载,因此两个线程都会尝试加载 InnerHolder 类。然而,由于类的加载和初始化是互斥的,只会有一个线程首先进入 InnerHolder 类的初始化阶段,此时会创建 Singleton 的实例并赋值给 INSTANCE 静态变量。由于此时 INSTANCE 已经被赋值,因此另一个线程在进入 InnerHolder 类的初始化阶段时会直接返回 INSTANCE,而不会重新创建实例。因此,即使同时调用 getInstance() 方法,也只会得到同一个 Singleton 实例,不会出现线程安全问题。
同时,这种写法也实现了懒加载,如果不调用 getInstance () 方法,就不会调用静态内部类对 Singleton 进行实例化。

枚举单例 推荐

枚举单利不能反射调用,安全,别的容易被 hook
我们知道,枚举在 Java 中与普通的类是一样的,可以有自己的属性,还可以有自己的方法,最重要的是,枚举实例的创建是线程安全的,并且在任何情况下它都是一个单例(包括反序列化)

1
2
3
4
5
6
7
8
public enum EnumSingleton {
     //定义一个枚举的元素,它就是 Singleton 的一个实例
    INSTANCE;  

    public void doSomeThing() {  
         // do something...
    }  
}

反编译成 java 代码:

1
2
3
4
5
6
7
8
9
10
11
12
public final class EnumSingleton extends Enum<EnumSingleton> {
        public static final  EnumSingleton  ENUMSINGLETON;
        private static final Singleton ENUM$VALUES\[];

        public static  EnumSingleton[] values ();
        public static  EnumSingleton valueOf (String s);
        static {
            ENUM$VALUES = (new EnumSingleton[] {
                ENUMSINGLETON
            });
        };
}

通过反编译后,可以看到单例枚举类是一个 final 类,且创建该对象的实例是在一个 static 静态语句块中进行的,根据 JVM 的类加载机制,静态语句块只会在类被加载时执行一次,所以可以线程安全。另外因为单例枚举类反编译后实际上是一个被 final 修饰的类,所以他不能被继承,也就不能创建子类对象。

总结
饿汉:无法对instance实例进行延迟加载

懒汉:多线程并发情况下无法保证实例的唯一性

懒汉线程安全:使用synchronized导致性能缺陷

DCL:JVM即使编译器的指令重排序

静态内部类/枚举:延迟加载/线程安全/性能优势

Buidler (建造者)模式

一种创建型的设计模式,通常用来将一个复杂的对象的构造过程分离, 让使用者可以根据需要选择创建过程。另外, 当这个复杂的对象的构造包含很多可选参数时, 也可以使用建造者模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class AlerDialog {

    private  String title;
    private  String message;

    public AlerDialog(Builder builder) {
//        View.inflate()
        this.title = builder.title;
        this.message = builder.message;
    }

    public static class Builder {
        private  String title;
        private  String message;

        public  Builder setTitle(String title) {
            this.title = title;
            return this;
        }

        public   Builder setMessage(String message) {
            this.message = message;
            return this;
        }

        public  AlerDialog build() {
            return new AlerDialog(this);
        }
    }
}
new AlerDialog.Builder().setTitle("").setMessage("").build();

原型模式

原型模式主要解决的问题就是创建重复对象,而这部分对象内容本身比较复杂,生成过程可能从库或者
RPC接口中获取数据的耗时较长,因此采用克隆的方式节省时间。
在原型模式中所需要的非常重要的手段就是克隆,在需要用到克隆的类中都需要实现implementsCloneable接口
不常用

工厂模式

工厂类可以根据条件生成不同的子类实例,这些子类有一个公共的抽象父类并且实现了相同的方法,但是这些方法针对不同的数据进行了不同的操作(多态方法)。当得到子类的实例后,开发人员可以调用基类中的方法而不必考虑到底返回的是哪一个子类的实例。
工厂类可以根据条件生成不同的子类实例,并且这些对象需要具有共同的接口。
工厂方法模式(Factory Method)分为3种:

普通工厂模式

就是建立一个工厂类,对实现了同一接口的一些类进行实例的创建。首先看下关系图:

unknown_filename.4|657x0

我们以一个例子来讲解:发送短信和发送邮件(具有共同的接口:发送)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
  public interface Sender {  
    /*
     * 发送邮件或者短消息的共同接口
     */  
    public void sender();  
}  

/*
* 发送邮件的实现类
*/  
public class MailSender implements Sender {  

    public void sender() {  
        System.out.println("this is mailsender!");  
    }  
}
/*
* 发送短信的实现类
*/  
public class SMSSender implements Sender {  

    public void sender() {  
        // TODO Auto-generated method stub  
        System.out.println("this is sms sender!");  
    }  

}
public class SendFactory {  
    public Sender produce(String type){  
        if ("mial".equals(type)) {  
            //根据类型生产对象  
            return new MailSender();  
        }else if("sms".equals(type)) {  
            //根据类型生产对象  
            return new SMSSender();  
        }else {  
            System.out.println("类型输入错误");  
            return null;  
        }  
    }  
}  


工厂方法模式

这是对普通工厂方法模式的改进,在普通工厂方法模式中,如果传递的字符串出错,则不能正确创建对象,而多个工厂方法模式是提供多个工厂方法,分别创建对象。关系图:

unknown_filename.2|678x0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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();  
    }  
}

静态工厂模式

将上面的多个工厂方法模式里的方法置为静态的,不需要创建实例,直接调用即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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();  
    }  
}  

工厂模式适用于:凡是出现了大量的产品需要创建,并且具有共同的接口时,可以通过工厂方法模式进行创建。在以上的三种模式中,第一种如果传入的字符串有误,不能正确创建对象,第三种相对于第二种,不需要实例化工厂类,所以,大多数情况下,我们会选用第三种——静态工厂方法模式。

抽象工厂模式

(Abstract Factory)

工厂方法模式有一个问题就是,类的创建依赖工厂类,也就是说,如果想要拓展程序,必须对工厂类进行修改,这违背了闭包原则,所以,从设计角度考虑,有一定的问题,如何解决?就用到抽象工厂模式,创建多个工厂类,这样一旦需要增加新的功能,直接增加新的工厂类就可以了,不需要修改之前的代码。因为抽象工厂不太好理解,我们先看看图,然后就和代码,就比较容易理解。

unknown_filename.3|724x0

这个模式的好处就是,如果你现在想增加一个功能:发及时信息,则只需做一个实现类,实现Sender接口,同时做一个工厂类,实现Provider接口,就OK了,无需去改动现成的代码。这样做,拓展性较好!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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();  
    }  
}

简单工厂

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class FragmentFactory {
    private static Map<Integer, Fragment> mFragments = new HashMap<Integer, Fragment>();
    public static Fragment createFragment(int position) {
        Fragment fragment = null;
        fragment = mFragments.get(position);  //在集合中取出来Fragment
        if (fragment == null) {  //如果再集合中没有取出来 需要重新创建
            if (position == 0) {
                fragment = new HomeFragment();
            } else if (position == 1) {
                fragment = new AppFragment();
            } 
            if (fragment != null) {
                mFragments.put(position, fragment);// 把创建好的Fragment存放到集合中缓存起来
            }
        }
        return fragment;
    }

设计模式1(创建型模式)
http://peiniwan.github.io/2024/04/930cfc2b3518.html
作者
六月的雨
发布于
2024年4月6日
许可协议