设计原则读书笔记(一)
设计原则读书笔记(一)
| 这个作业属于哪个课程 | https://edu.cnblogs.com/campus/gdgy/2021Softwarecodedevelopmenttechnology |
|---|---|
| 这个作业要求在哪里 | https://edu.cnblogs.com/campus/gdgy/2021Softwarecodedevelopmenttechnology/homework/11833 |
| 这个作业的目标 | 写与软件设计模式有关书籍的读书笔记 |
参考书籍
- 软件秘笈:设计模式那点事
设计原则的意义
使我们从复杂、烦乱的代码解脱出来,让软件系统变得更加稳定、更易于扩展、维护更轻松。
说白了就是:防止你写屎山代码,教你一些代码结构的技巧,让程序稳定可扩展
设计模式的分类
| 创建型设计模式 | 结构型模式 | 行为型模式 |
|---|---|---|
| 工厂方法模式 抽象方法模式 建造者模式 原型模式 单例模式 |
适配器模式 桥接模式 组合模式 装饰者模式 外观模式 享元模式 代理模式 |
责任链模式 命令模式 解释器模式 迭代器模式 中介者模式 备忘录模式 观察者模式 状态模式 策略模式 模板方法模式 访问者模式 |
常用的设计模式
1. 建造者模式
-
为什么要使用建造者模式
当一个类的构造方法需要传过多的参数并且有些参数是可选的时候可以考虑采用建造者模式。如果采用普通的构造方法的话就要写好多好多种构造方法,来处理参数可选的情况。
-
示例:Android Okhttp3网络请求框架
如果我们要创建一个OkhttpClient 对象来做网络请求,我们需要这么写代码:
this.okHttpClient = new OkHttpClient.Builder() .cache(cache) .addInterceptor(interceptor) .addNetworkInterceptor(chain -> { Response originalResponse = chain.proceed(chain.request()); //省略代码 return originalResponse; }) //请求添加cookie .addNetworkInterceptor(chain -> { Request request; //省略代码 return chain.proceed(request); }) .connectTimeout(15, TimeUnit.SECONDS) .build();我们可以看到,我们可能需要给这个okhttpClient设置一些拦截器,设置超时时间,设置缓存之类的属性。但是你也可以不用添加拦截器,这里拦截器是用来拦截cookie的。所以对于这种对象的属性比较多,但是不是每个同时都得必须用得上的时候就可以用建造者模式来初始化对象。我们可以来看看OkHttpClient.Builder这个类的源码
Builder是OkHttpClient的子类,这个子类里面定义了与OkHttpClient同样的属性

同样的属性OkhttpClient里面也有

我们再来随便找一个刚刚设置属性的方法,比如说Builder().connectTimeout()
public Builder callTimeout(long timeout, TimeUnit unit) { callTimeout = checkDuration("timeout", timeout, unit); return this; } 可以看到,callTimeout在检查了超时时间这个输入是否合法后,将时间赋值给了callTimeout这个变量,然后返回了Builder自身
再来看看刚刚创建OkhttpClient最后调用的Builder().build()方法
public OkHttpClient build() { return new OkHttpClient(this); } 这里调用了一个OkHttpCilent类的构造方法,看下这个构造方法,这个方法比较长,我省略了一部分
OkHttpClient(Builder builder) { this.dispatcher = builder.dispatcher; this.cache = builder.cache; this.internalCache = builder.internalCache; this.socketFactory = builder.socketFactory; this.callTimeout = builder.callTimeout; this.connectTimeout = builder.connectTimeout; this.readTimeout = builder.readTimeout; this.writeTimeout = builder.writeTimeout; this.pingInterval = builder.pingInterval; if (interceptors.contains(null)) { throw new IllegalStateException("Null interceptor: " + interceptors); } if (networkInterceptors.contains(null)) { throw new IllegalStateException("Null network interceptor: " + networkInterceptors); } } 可以看到,调用这个构造方法时,就可以直接把builder对象的属性传给父类OkHttpClient。这样就不用对OkHttpClient类写很多个构造方法了。而且写代码的时候也是流式创建对象,不像构造方法一样括号里面一堆参数填进去,不优雅。
this.okHttpClient = new OkHttpClient.Builder() .cache(cache) .addInterceptor(interceptor) .addNetworkInterceptor(netWorkInterceptor) //请求添加cookie .addNetworkInterceptor(netWorkInterceptor2) .connectTimeout(15, TimeUnit.SECONDS) .build(); //而不是这样 这样写显然可读性不好 this.okHttpClient = new OkHttpClient(cache,interceptor,netWorkInterceptor,netWorkInterceptor2,15, TimeUnit.SECONDS)这就是建造者模式最核心的思想了。
由于安卓的OkHttpClient这里的建造者模式比较简单,实际上还可以扩展一下,上述例子中只有产品(OkHttpClient对象),具体建造者(OkHttpClient.Builder),和客户(使用OkHttpClient对象的类)。实际上为了更好的解耦还可以把OkHttpClient.Builder的功能进行分割,比如设置拦截器的放到一个拦截器Builder里面,设置超时缓存这些基本的放到一个Builder里面,然后这两个Builder继承自一个抽象Builder的类,这个抽象Builder交给一个指挥者去指挥这些具体的Builder做些什么事情。详情可以看看这篇博客
http://c.biancheng.net/view/1354.html
但是实际上过多的解耦对于这个OkHttpClient不合适,没有必要解耦。放在一起一个Builder会做全部事情就好了。过多的解耦会增加代码量。
2. 模板方法模式
-
为什么要使用模板方法模式
可能项目中有些类A都要继承某一个类B,同时都要有共同的方法,这个时候就可以写一个抽象类C去给这些类提供一个模板,然后A去继承C,C继承B。这是一种简单的模板方法模式
-
示例
在安卓开发中,可能很多类都要继承自Activity这个类,然后按照惯例都是要找控件或者是从网络/数据库中获取一些数据,这两个方法分别叫做
initView()和initData()。如果我们每写一个这种类都要重新写一遍这种方法的时候会觉得很累,能不能减轻点工作量呢?我们可以先写一个BaseActivity这个类,这是一个抽象类,代码如下:
public abstract class BaseActivity<P extends BasePresenter> extends AppCompatActivity implements BaseView { protected P presenter; /** * 创建 presenter * * @return presenter */ protected abstract P createPresenter(); /** * 得到布局文件 id * * @return layout id */ protected abstract int getLayoutId(); /** * 初始化布局 */ protected abstract void initView(); /** * 初始化数据 */ protected abstract void initData(); @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); //设置竖屏 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); setContentView(LayoutInflater.from(this).inflate(getLayoutId(), null)); ButterKnife.bind(this); presenter = createPresenter(); initView(); initData(); } @Override protected void onDestroy() { super.onDestroy(); XUtil.dismissLoading(); //销毁时,解除绑定 if (presenter != null) { presenter.detachView(); } } @Override public void showLoading() { XUtil.showLoading(XUtil.getString(R.string.loading)); } @Override public void hideLoading() { XUtil.dismissLoading(); } /** * 可以处理异常 */ @Override public void onErrorCode(BaseBean bean) { } } 然后我们写新的类就不要继承自Activity了,继承自BaseActivity

引入方法,就大致有个框架了
public class TestActivity extends BaseActivity { @Override protected BasePresenter createPresenter() { return null; } @Override protected int getLayoutId() { return 0; } @Override protected void initView() { } @Override protected void initData() { } }优点: 1、封装不变部分,扩展可变部分。 2、提取公共代码,便于维护。 3、行为由父类控制,子类实现。
缺点:每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。
3. 单例模式
-
为什么要使用单例模式
还是拿上面的OkHttpClient说,一般一个App只需要一个访问网络,做请求的客户端。这种情况下就需要使用单例模式了。不要开一个新的界面就随便写一个新的Client做网络请求,这样既浪费资源,还容易处理不当造成内存泄露,代码也非常混乱。
-
介绍
单例模式有好多种,先分为懒汉和恶汉,懒汉就是需要用到的时候才去创建,饿汉就是一开始就创建。各有优劣,前者前期省内存,但是第一次用就需要实例化,所以第一次请求时间就会比较久。恶汉就是一开始就创建了,需要占用内存空间
然后创建单例模式的方法又有好多张,什么DCL双重检查,静态内部类,枚举等等。一般常用的就是这些了,这里只介绍前两种
这里直接拷贝我原来博客写的
懒汉式的单例为什么要使用双重检查模式(DCL)?
double check指的是下面这段代码
public class Singleton { private volatile static Singleton instance = null; private Singleton() {} public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) {// 1 if (instance == null) {// 2 instance = new Singleton();// 3 } } } return instance; }这种叫做双重检查模式(DCL)
第一次是为了减少不必要的同步,提升性能
第二次是在Singleton为空才创建实例 虽然volatile会耗费一点时间但是值得
volatile的作用是避免程序出错 防止指令重排序带来的不良影响。比如
a=1 a=2 a=3编译器就会省略前面 直接a=3
一般来说,处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。这个就是指令重排序。
那我们那里可能会发生指令重排序呢,是在instance = new Singleton()这里。这里经过编译不是一个原子操作,他大概分为以下几步
- 分配内存
- 变量赋值
- 初始化对象
当多线程来的时候可能一个线程正搞完第2步,他不为空了,还没搞第三步,另一个线程就来判断,嗯 不为空?直接修改,这是不对的。
它在单线程,无并发没什么紧要,但是多线程就会有影响,加了这个就能确保程序的正确性
它相比普通的线程安全的懒汉模式的好处是避免了不必要的同步等待的时间
为什么他会省时间呢?
- 因为我们知道只有为空的时候才需要同步,才需要加锁和释放锁。所以第一个判断就是判断有没有被初始化,没有初始化再去获取锁,如果已经初始化就直接返回
- 第二次检查变量是否已经被初始化:如果其他线程曾获取过锁,那么变量已被初始化,返回初始化的变量。
- 如果第二次还没有被初始化 那就初始化并且返回变量
它的缺点就是第一次加载反应会慢一些,还有就是可能会dcl失效。dcl失效我不太懂,但是查阅资料感觉还是和指令重排序或者指令的执行顺序有关。可以看看
https://www.jianshu.com/p/c647fe58b98d
静态内部类的方式创建单例创建单例的好处就是加载Singleton 类的时候不会初始化instance,只有调用getInstance()的时候才加载SingletonHolder并初始化instance。这样不仅能保证线程安全,也能保证Singleton类的唯一性。好处就是你用的时候才去占用内存,省空间。
public class SingleTon{ private SingleTon(){} private static class SingleTonHoler{ private static SingleTon INSTANCE = new SingleTon(); } public static SingleTon getInstance(){ return SingleTonHoler.INSTANCE; } }下面是复制的
虚拟机会保证一个类的()方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的()方法,其他线程都需要阻塞等待,直到活动线程执行()方法完毕。如果在一个类的()方法中有耗时很长的操作,就可能造成多个进程阻塞(需要注意的是,其他线程虽然会被阻塞,但如果执行()方法后,其他线程唤醒之后不会再次进入()方法。同一个加载器下,一个类型只会初始化一次。),在实际应用中,这种阻塞往往是很隐蔽的。
故而,可以看出INSTANCE在创建过程中是线程安全的,所以说静态内部类形式的单例可保证线程安全,也能保证单例的唯一性,同时也延迟了单例的实例化。
=============================
那么也就是说缺点就是可能造成进程阻塞。不过创建一个单例一般很快就好了,没啥影响。
4. 适配器模式
咕咕咕
5. 责任链模式
也拿OkHttp讲吧,先放着,明天或者晚上再写


浙公网安备 33010602011771号