您现在的位置是:首页 >技术交流 >设计模式系列网站首页技术交流
设计模式系列
文章目录
一,单例模式
确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。这样可以避免产生多个对象消耗过多的资源。
单例模式主要有几个关键点:
- 构造函数不对外开放,一般为private
- 通过一个静态方法或者枚举返回单例类对象
- 确保单例类的对象有且只有一个,尤其是在多线程环境下
- 确保单例类对象在反序列化时不会重新构建对象
1.1 懒汉模式(不建议采用)
声明一个静态对象,第一次调用getInstance时进行初始化。
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
可以看出getInstance()方法添加了synchronized 关键字,多线程下保证同步。但是问题是每次调用getInstance()方法都会进行同步,这样会消耗不必要的资源,因此不建议用这种模式。
1.2 双检查模式(大部分场景可用)
public class Singleton {
private static volatile Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
可以看出双检查模式下既能够在需要的时候初始化,又能保证线程安全,而且同步锁只执行一次。
1.3 静态内部类模式(最佳实现)
public class Singleton {
private Singleton() {
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
private static class SingletonHolder {
private static final Singleton instance = new Singleton();
}
}
双检查模式虽然在一定程度上解决资源消耗,多余同步,线程安全等问题,但是在一些情况下依然会出现失效。
静态内部模式可以有效解决这些问题,在第一次调用getInstance()方法虚拟机加载SingletonHolder,保证了线程安全和实例唯一。
1.4 枚举单例
public enum Singleton {
INSTANCE;
public void test(){
System.out.print("test");
}
}
枚举单例是最简单,也是最有效的。枚举实例的创建是线程安全的,并且本身就是单例的。还有枚举即使在反序列化的情况下也是单例的。
1.5 容器模式
public class Singleton {
private static Map<String, Object> objectMap = new HashMap<>(0);
private Singleton() {
}
public static void addInstance(String key, Object instance) {
if (!objectMap.containsKey(key)) {
objectMap.put(key, instance);
}
}
public static Object getService(String key) {
return objectMap.get(key);
}
}
这种方式也能实现单例,把实例类保存在集合中,也能保证唯一性,并且可以管理多种类。
在项目开发中如何选择单例,这得看具体的需求,和自身的开发环境等问题。
二,工厂模式
2.1 定义
工厂模式是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
2.2 场景1
现在有一个需求,图书分类。图书馆有很多书,我们要对书进行分类,有历史类,科学类等。
首先我们要创建图书的抽象类
public abstract class Book {
/**
* 抽象方法
*/
public abstract void method();
}
具体的分类实现
//历史类
public class HistoryBook extends Book {
@Override
public void method() {
System.out.print("我是历史类");
}
}
//科学类
public class ScienceBook extends Book {
@Override
public void method() {
System.out.print("我是科学类");
}
}
抽象工厂类
public abstract class Factory {
/**
* 抽象工厂方法
* @return
*/
public abstract Book createBook();
}
具体的工厂实现
public class ConcreteFactory extends Factory {
@Override
public Book createBook() {
return new HistoryBook();//历史类实现
//return new ScienceBook();//科技类实现
}
}
调用工厂实现图书分类
Factory factory = new ConcreteFactory();
Book book = factory.createBook();
book.method();
到这里整个实现都完成了,代码很简单。主要就是四大模块,第一是抽象图书类,第二是具体的实现图书类,第三是抽象工厂类,第四是具体的实现工厂类。如果我们想实现哪个分类就在ConcreteFactory中实现即可,但是这样不同的实现都需要改ConcreteFactory 中的代码,因此我们对上面实现方式进行改造。
改造抽象工厂类
public abstract class Factory {
/**
* 抽象工厂方法
* @return
*/
public abstract <T extends Book> T createBook(Class<T> cla);
}
改造抽象工厂实现类
public class ConcreteFactory extends Factory {
/**
* 抽象工厂具体实现
* @param cla
* @param <T>
* @return
*/
@Override
public <T extends Book> T createBook(Class<T> cla) {
Book book=null;
try {
book= (Book) Class.forName(cla.getName()).newInstance();
}catch (Exception e){
e.printStackTrace();
}
return (T)book;
}
}
程序中调用,需要哪个分类就传入那个分类的类型
Factory factory = new ConcreteFactory();
Book book = factory.createBook(HistoryBook.class);
//book = factory.createBook(ScienceBook.class);
book.method();
2.3 场景2
我们知道在Android中数据存储有五种方式:SharedPreferences,SQLite,文件,ContentProvider,网络,像前三中存储方式比较类似,我们可以把他们抽象化为统一的接口,进行统一的调用和管理。
抽象接口,提供统一的数据操作接口。
public abstract class ICallBackStorage<T> {
/**
* 添加数据
* @param id
* @param t
*/
public abstract void add(String id,T t);
/**
* 删除数据
* @param t
*/
public abstract void remove(T t);
/**
* 更新数据
* @param id
* @param t
*/
public abstract void update(String id,T t);
/**
* 查询数据
* @param id
*/
public abstract Object query(String id);
}
文件存储实现
public class FileStorage extends ICallBackStorage<File> {
@Override
public void add(String id,File file) {
//添加文件数据
}
@Override
public void remove(File file) {
//删除文件数据
}
@Override
public void update(String id,File file) {
//更新文件数据
}
@Override
public Object query(String id) {
//查询文件数据
return null;
}
}
SharedPreferences存储实现
public class SharedStorage extends ICallBackStorage<String> {
@Override
public void add(String id, String s) {
//添加SharedPreferences数据
}
@Override
public void remove(String s) {
//删除SharedPreferences数据
}
@Override
public void update(String id, String s) {
//更新SharedPreferences数据
}
@Override
public Object query(String id) {
//查询SharedPreferences数据
return null;
}
}
数据库存储实现
public class SqlStorage extends ICallBackStorage<Object> {
@Override
public void add(String id, Object o) {
//添加数据
}
@Override
public void remove(Object o) {
//删除数据
}
@Override
public void update(String id, Object o) {
//更新数据
}
@Override
public Object query(String id) {
//查询数据
return null;
}
}
抽象工厂类
public abstract class Factory {
/**
* 抽象工厂方法
* @return
*/
public abstract <T extends ICallBackStorage> T createBook(Class<T> cla);
}
抽象工厂实现类
public class ConcreteFactory extends Factory {
@Override
public <T extends ICallBackStorage> T createBook(Class<T> cla) {
ICallBackStorage<T> io=null;
try {
io= (ICallBackStorage<T>) Class.forName(cla.getName()).newInstance();
}catch (Exception e){
e.printStackTrace();
}
return (T)io;
}
}
调用方式
Factory factory = new ConcreteFactory();
ICallBackStorage io = factory.createBook(SqlStorage.class);
io.add("1", "第一条数据");
2.4 总结
工厂模式的优点:
- 分离接口与实现,隔离了具体类的生产,使得客户端并不需要知道什么被创建。
- 当一个产品中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。
- 增加新的产品类时更加的灵活,易用。符合 “开闭原则”。
工厂模式的缺点:
- 类文件爆炸性增加,扩展新的产品类方法不太容易,每当增加一个产品类方法所有的具体实现类都需要修改。
三,代理模式
基本概念:代理模式也成为委托模式,目标对象为其对象提供一种代理对象,其他对象通过代理对象来控制对目标对象的访问。
3.1 代理模式的作用
代理模式就相当于中介,生活中我们要找房子,法律诉讼,代购都可以理解为代理模式。在程序中,有些模块我们是无法访问的,因为有些模块不能对外公开。但是我们又想用模块中的一些功能,这时代理就产生了,我们可以写个代理类帮我们实现。在我们日常开发中,如果我们要重构代码,当我们要更改目标对象时,目标对象被很多地方引用,因此修改成本太高。此时使用代理模式,通过对代理对象的修改,而完成最终的业务需求。
实现生活中的代理模式,以法律诉讼为例,如果要打官司肯定需要找个律师为自己的诉讼代理人,李明因为老板欠薪要打官司。
诉讼接口类:
public interface ILawsuit {
void submit();//提交申请
void burden();//进行举证
void defend();//开始辩护
void finish();//完成诉讼
}
具体诉讼人
public class LiMing implements ILawsuit {
@Override
public void submit() {
System.out.println("老板欠工资,申请仲裁");
}
@Override
public void burden() {
System.out.println("银行流水,证明欠薪");
}
@Override
public void defend() {
System.out.println("证据确凿");
}
@Override
public void finish() {
System.out.println("诉讼成功");
}
}
代理律师
public class Lawyer implements ILawsuit {
private ILawsuit mLawsuit;//持有一个具体被代理的引用
public Lawyer(ILawsuit lawsuit) {
mLawsuit = lawsuit;
}
@Override
public void submit() {
mLawsuit.submit();
}
@Override
public void burden() {
mLawsuit.burden();
}
@Override
public void defend() {
mLawsuit.defend();
}
@Override
public void finish() {
mLawsuit.finish();
}
}
客户类
public class Test {
public static void main(String[] args){
//构造一个李明
ILawsuit liming=new LiMing();
//构造一个代理律师并将李明作为构造参数
ILawsuit lawyer=new Lawyer(liming);
//律师提交诉讼申请
lawyer.submit();
//律师进行举证
lawyer.burden();
//律师替代李明进行辩护
lawyer.defend();
//完成诉讼
lawyer.finish();
}
}
到这里整个代理就结束了,当然一个律师可以为多个人代理,只需要再定义个类实现ILawsuit即可。
3.2 代理模式可以分为两种
- 静态代理:静态代理如上述那样,在代码运行前代理类的class编译文件已经存在。缺点,代理对象需要与目标对象实现一样的接口,会有很多代理类,一旦接口增加方法,目标对象与代理对象都要维护。
- 动态代理:动态代理和静态代理恰恰相反,动态代理是通过反射机制动态生成代理对象的,在程序没有执行前不需要知道代理是谁。
动态代理类
可以看出代理类不需要实现目标对象的接口。
public class DynamicProxy implements InvocationHandler {
private Object obj;//被代理引用
public DynamicProxy(Object obj){
this.obj=obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result=method.invoke(obj,args);//调用被代理类对象的方法
return result;
}
}
修改客户类,实现动态代理
public class Test {
public static void main(String[] args) {
//构造一个李明
ILawsuit liming = new LiMing();
//构造一个动态代理
DynamicProxy proxy = new DynamicProxy(liming);
//获取被代理类李明的ClassLoader
ClassLoader loader = liming.getClass().getClassLoader();
//动态构造一个代理者律师
ILawsuit lawyer = (ILawsuit) Proxy.newProxyInstance(loader, new Class[]{ILawsuit.class}, proxy);
//律师提交诉讼申请
lawyer.submit();
//律师进行举证
lawyer.burden();
//律师代替李明进行辩护
lawyer.defend();
//完成诉讼
lawyer.finish();
}
}
static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h )
该方法是在Proxy类中是静态方法,接收的三个参数依次为:
- ClassLoader loader:指定当前目标对象使用类加载器,获取加载器的方法是固定的。
- Class<?>[] interfaces:目标对象实现的接口的类型,使用泛型方式确认类型。
- InvocationHandler h:事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入。
3.3 代理模式实战
在Android开发中,我们经常会用到通知栏的功能,也就是NotificationManager。NotificationManager如何使用也很简单,但是API的不同,很多方法,样式也不一样,因此我们就要做适配。为每种不同的Notification样式定义一个抽象类,这里以正常64dp Height,256dp Height,和 headsUpContentView为例。
public abstract class Notify {
private Context context;
private NotificationManager nm;
private NotificationCompat.Builder builder;
public Notify(Context context) {
this.context = context;
nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
builder = new NotificationCompat.Builder(context);
builder.setSmallIcon(R.mipmap.ic_launcher)
.setContentIntent(PendingIntent.getActivity(context, 0,
new Intent(context, MainActivity.class),
PendingIntent.FLAG_UPDATE_CURRENT));
}
/**
* 发送一条通知
*/
public abstract void send();
/**
* 取消一条通知
*/
public abstract void cancel();
}
64dp Height的Notification实现
public class NotifyNormal extends Notify {
public NotifyNormal(Context context) {
super(context);
}
@Override
public void send() {
Notification n = builder.build();
n.contentView = new RemoteViews(context.getPackageName(), R.layout.test);
nm.notify(0, n);
}
@Override
public void cancel() {
nm.cancel(0);
}
}
256dp Height的Notification实现
public class NotifyBig extends Notify {
public NotifyBig(Context context) {
super(context);
}
@Override
public void send() {
Notification n = builder.build();
n.contentView = new RemoteViews(context.getPackageName(), R.layout.test);
n.bigContentView = new RemoteViews(context.getPackageName(), R.layout.test);
nm.notify(0, n);
}
@Override
public void cancel() {
nm.cancel(0);
}
}
headsUpContentView的Notification实现
public class NotifyHeadsUp extends Notify {
public NotifyHeadsUp(Context context) {
super(context);
}
@Override
public void send() {
Notification n = builder.build();
n.contentView = new RemoteViews(context.getPackageName(), R.layout.activity_main);
n.bigContentView = new RemoteViews(context.getPackageName(), R.layout.activity_main);
n.headsUpContentView = new RemoteViews(context.getPackageName(), R.layout.activity_main);
nm.notify(0, n);
}
@Override
public void cancel() {
nm.cancel(0);
}
}
代理类
public class NotifyProxy extends Notify {
private Notify notify;
public NotifyProxy(Context context) {
super(context);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
notify = new NotifyHeadsUp(context);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
notify = new NotifyBig(context);
} else {
notify = new NotifyNormal(context);
}
}
@Override
public void send() {
notify.send();
}
@Override
public void cancel() {
notify.cancel();
}
}
以上就实现了整个代理过程,是不是很简单。
四,状态模式
4.1 基本概念
状态模式把对象的行为包装在不同的状态对象里,每一个状态对象都有一个共同的抽象状态基类。状态模式的意图是让一个对象在其内部状态改变的时候,其行为也随之改变。
使用场景
- 一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为。
- 代码中包含大量与对象状态有关的条件语句(if-else 或switch-case)。
4.2 示例
模拟电视遥控器操作,在关机状态下,遥控器只能开机。在开机状态下,遥控器可以控制音量等。
定义电视操作接口
/**
* 电视状态接口
*/
public interface TvState {
void nextChannel();//下一频道
void prevChannel();//上一频道
void turnUp();//音量增加
void turnDown();//音量减小
}
关机状态类
/**
* 关机状态,此时只有开机功能是有效的
*/
public class PowerOffState implements TvState {
@Override
public void nextChannel() {
}
@Override
public void prevChannel() {
}
@Override
public void turnUp() {
}
@Override
public void turnDown() {
}
}
开机状态类
/**
* 开机状态
*/
public class PowerOnState implements TvState {
@Override
public void nextChannel() {
System.out.print("下一频道");
}
@Override
public void prevChannel() {
System.out.print("上一频道");
}
@Override
public void turnUp() {
System.out.print("增大音量");
}
@Override
public void turnDown() {
System.out.print("减小音量");
}
}
电源操作接口
/**
* 电源操作
*/
public interface PowerController {
void powerOn();
void powerOff();
}
控制器
public class TvController implements PowerController {
TvState mTvState;
public void setTvState(TvState tvState) {
this.mTvState = tvState;
}
@Override
public void powerOn() {
setTvState(new PowerOnState());
System.out.print("开机啦");
}
@Override
public void powerOff() {
setTvState(new PowerOffState());
System.out.print("关机啦");
}
public void nextChannel(){
mTvState.nextChannel();
}
public void prevChannel(){
mTvState.prevChannel();
}
public void turnUp(){
mTvState.turnUp();
}
public void turnDown(){
mTvState.turnDown();
}
}
使用方式,关机后再调用音量减小不起作用
TvController tvController = new TvController();
//开机
tvController.powerOn();
//下一频道
tvController.nextChannel();
//增加音量
tvController.turnUp();
//关机
tvController.powerOff();
//减小音量
tvController.turnDown();
到这里一个简单的状态模式就完成了,我看可以看出没有一个地方用到条件判断。状态模式的好处就是使我们的代码条理更加清晰,状态维护更加的方便。如果使用硬编码的方式,会出现很多if-else语句,代码可读性差而且也不易于维护。