设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结使用
设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样
看过好多关于设计模式的文章,这篇还是不错的,讲的很详细。
从定义上就能看出设计模式的重要性和实用性。在代码中使用合适的设计模式可以在提升代码结构合理性,降低耦合度。常用的设计模式有23种,这里只列举出目前用到过的,其他的以后再补充。
1.单例模式
单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中一个类只有一个实例。即一个类只有一个对象实例。
- 从单例模式的定义也能看出他的大概使用场景,它可以用来控制实例的数量。
注意,因为这个模式本身并不难,而且用起来会省去大量的创建实例代码,所以项目中会经常用到。
但是一定要注意使用场景,不然会出现很多意想不到的问题。- 写网络请求框架的时候注意,并发请求的时候,使用单例会造成结果异常。
- 创建Fragment 的时候使用单例,会造成内存泄露。
单例模式的写法
1.普通写法
-私有化构造方法,保证只有在类内部才能创建实例
-定义一个私有的实例变量
-向外提供一个创建实例的方法
public class BaseSingleton {
private static BaseSingleton mInstance;
//私有化构造方法,不让在外部直接创建对象
private BaseSingleton(){
}
//提供一个公有的方法提供对象实例
public static BaseSingleton getInstance() {
if (mInstance==null) {
mInstance=new BaseSingleton();
}
return mInstance;
}
}
上述代码直接用BaseSingleton.getInstance()即可创建实例,但是在多线程的情况下是不安全的。可能会创建多个实例,违背了该模式的初衷。因此需要改进.
改进1:
在getInstance()方法上直接加锁,可以解决线程安全问题
/**为解决线程安全问题,直接在方法上加synchronized
* 1.每次调用getInstance都要对对象上锁,造成性能打折
* 2.我们只需在第一次调用的时候加锁即可
* @return
*/
public synchronized BaseSingleton getInstance2() {
if (mInstance==null) {
mInstance=new BaseSingleton();
}
return mInstance;
}
这种解决办法造成的问题也在注释上说明了,因为每次调用getInstance()方法,不管是不是已经创建过对象,都会加锁(理论上我们只需要在第一次创建对象的时候加锁即可),所以会降低性能,并不是一个合理的方案。
继续改进,既然不能直接在创建方法上添加synchronized,那么我们就在第一次创建对象的时候加锁。
/**新问题
* 1.java指令中创建对象和赋值操作是分开进行的,也就是说mInstance=new BaseSingleton();语句是分两步执行的
* 但是jvm并不能保证这两个操作的执行顺序,也就是说JVm有可能先给实例分配空间,然后直接赋值给mInstance,然后再去初始化Singleton实例,
* 这样就可能出错
*
* 我们以A、B两个线程为例:
a>A、B线程同时进入了第一个if判断
b>A首先进入synchronized块,由于instance为null,所以它执行instance = new Singleton();
c>由于JVM内部的优化机制,JVM先画出了一些分配给Singleton实例的空白内存,并赋值给instance成员(注意此时JVM没有开始初始化这个实例),然后A离开了synchronized块。
d>B进入synchronized块,由于instance此时不是null,因此它马上离开了synchronized块并将结果返回给调用该方法的程序。
e>此时B线程打算使用Singleton实例,却发现它没有被初始化,于是错误发生了。
* @return
*/
public synchronized BaseSingleton getInstance3() {
if (mInstance==null) {
synchronized (mInstance) {
if (mInstance==null) {
mInstance=new BaseSingleton();
}
}
}
return mInstance;
}
此种方法可以解决在方法上加锁的性能问题,但是也造成了新的问题。由于java机制的问题,对象的创建和赋值是分开进行的,因此不能保证返回的mInstance一定是有值的。有可能在调用getInstance的时候直接返回一个null,这就不能让人接受了。具体分析过程看注释。
那么我们怎么解决这个问题呢?
由于JVM机制可以保证类的加载过程是互斥的,也就是说JVM可以保证每个类都只加载一次。
所以我们可以使用内部类的形式来维护一个实例
/**通过内部类来维护单例的实现
* 1.JVM内部的机制能够保证当一个类在加载的时候,这个类的加载过程是互斥的。
* 2.当我们第一次调用getInstance4()的时候,JVm能够帮我们保证instance制备实例化一次,
* 3.并保证把赋值给instance 的内存初始化完毕
* @author WHH
*
*/
private static class SingletonFactory{
private static BaseSingleton mInstance=new BaseSingleton();
}
public static BaseSingleton getInstance4() {
return SingletonFactory.mInstance;
}
通过这种方式就可以创建出一个相对完美的单例了。 推荐使用这个
2.工厂模式
这里就不贴百度的定义了,这个模式有好几个细分的模式,简单工厂模式、工厂方法模式、静态工厂方法模式、抽象工厂方法模式等。这么一大堆的工厂模式到底该怎么区分呢?我是这么记忆的,简单工厂模式当然是最基础的了,最基础的当然也会是最简单的,那么什么是简单工厂模式呢?
简单工厂模式
-直接在工厂类中根据传入的type,创建不同的实例,就是简单工厂模式
这种模式有几个缺点:
- 如果type写错了会造成很大的影响。
- 要向创建实例,必须先创建工厂类对象。
//定义发送信息的接口
public interface ISender {
public void send(String content);
}
//发送邮件实现类
public class MailSender implements ISender {
@Override
public void send(String content) {
System.out.println("-------->发送邮件:"+content);
}
}
/**发送短信类,实现了ISender接口,重写send逻辑
* @author WHH
*
*/
public class SmsSender implements ISender {
@Override
public void send(String content) {
System.out.println("------------发送短信:"+content);
}
}
//工厂类
public class SendFactory {
/**普通工厂模式:
* 1.定义公共接口
* 2.定义公共接口的实现类,并重写具体逻辑
* 3.定义工厂类,根据传入的类型创建对应的实体类对象.
* @param type
* @return
*/
public ISender createSender(String type) {
if ("sms".equals(type)) {
return new SmsSender();
}else if ("mail".equals(type)) {
return new MailSender();
}else {
System.out.println("请输入正确的类型!");
return null;
}
}
}
//测试类,传入不同的type创建不同的实例
public class FactoryTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println("--------------------普通工厂模式-------------------");
SendFactory sendFactory=new SendFactory();
ISender sender=sendFactory.createSender("sms");
sender.send("----------------->发送短信:这是普通工厂模式");
sender=sendFactory.createSender("mail");
sender.send("----------------->发送邮件:这是普通工厂模式");
}
}
工厂方法模式
为了解决简单工厂方法传入变量的问题,对工厂方法加以改进就成了工厂方法。把创建实例的方法分开,每一种实例的创建都有对应的一个方法,这样就形成了工厂方法模式
/**工厂方法模式
* 就是把普通工厂方法模式需要传入类型,来创建对应类型对象的模式,
* 改成了直接调用对应的方法就可以创建对应的对象。
* 1.比普通工厂看起来更清晰
* 2.无需传参,不用担心参数传错
* @author WHH
*
*/
public class MultiSendFactory {
public ISender createSmsSender() {
return new SmsSender();
}
public ISender createMailSender() {
return new MailSender();
}
}
//创建实例的时候只需调用
System.out.println("--------------------工厂方法模式-------------------");
MultiSendFactory multiSendFactory=new MultiSendFactory();
ISender smsSender=multiSendFactory.createSmsSender();
smsSender.send("------------->发送短信:工厂方法模式");
smsSender=multiSendFactory.createMailSender();
smsSender.send("------------->发送邮件:工厂方法模式");
每次创建实例都要先创建工厂对象,很麻烦是吧?不要急,接下来就用到了静态工厂方法模式
3.静态工厂方法模式
解决了工厂方法模式创建繁琐的问题
/**静态工厂方法模式
* 1.就是把多工厂方法模式的创建方法用static修饰。
* 2.调用更加简单,static修饰的方法可以直接使用类名.调用。
* @author WHH
*
*/
public class StaticFactory {
public static ISender createSmsSender() {
return new SmsSender();
}
public static ISender createMailSender() {
return new MailSender();
}
}
4.抽象工厂模式
这个模式其实也不难理解,说白了就是把工厂也给抽出来了,抽成了一个专门的创建工厂的接口,目的是为了解决上述几种工厂方法的扩展性问题。不能每次增加新类都去修改Factory类吧。所以就把工厂类也给抽象出来了。这样的话如果需要新增工厂类,只需要实现工厂接口,重写其中的方法即可。
//工厂接口
public interface IFactory {
public ISender createSenderFactory();
}
//发送短信的工厂类
public class SmsFactory implements IFactory {
@Override
public ISender createSenderFactory() {
// TODO Auto-generated method stub
return new SmsSender();
}
}
//发送邮件的工厂类
public class MailFactory implements IFactory {
@Override
public ISender createSenderFactory() {
// TODO Auto-generated method stub
return new MailSender();
}
}
3.建造者模式
建造者模式可以用来创建一些有复杂逻辑的对象,比如说如果某个对象的一些方法必须按照特定的顺序执行,就可以采用建造者模式。通过独有的Builder类创建对象,用户无需担心方法的调用顺序,所有的一切都已经封装在了Builder类中。
使用步骤:
- 私有化构造方法,避免用户在外部直接创建对象,造成方法调用混乱
- 创建静态内部类Builder,Builder类需要向外提供相应属性方法的入口。
- 创建build方法,在该方法内实现外部类对象的创建,并确定外部类方法的调用顺序,并返回外部类对象.
举个栗子:
比如我们要创建一个Dialog类,必须要先执行initView方法来初始化各种组件,然后在调用setData方法填充数据,最后在调用show方法来显示。注意这里方法的调用顺序,必须先调用initView(),再嗲用setData(),不然会报空指针异常。所以我们这里就可以使用建造者模式来实现,代码如下
package com.hank.ok.dialogactivity;
import android.content.Context;
/**
* 类功能描述:建造者模式
* author:WHH
* create time: 2017-09-06 10:27
* version:${version}
*/
public class ReportDialog {
private Context mContext;
private ReportDialog(Context mContext) {
this.mContext = mContext;
}
private void initView() {
//初始化View
}
private void setData() {
//填充数据
}
public void show(){
//显示
}
public static class Build {
private Context mContext;
private String mTitle;
private String mContent;
public Build(Context mContext) {
this.mContext = mContext;
}
public Build setTitle(String title) {
this.mTitle = title;
return this;
}
public Build setContent(String content) {
this.mContent = content;
return this;
}
public ReportDialog build() {
ReportDialog dialog = new ReportDialog(mContext);
dialog.initView();
dialog.setData();
return dialog;
}
}
}