常见的设计模式
在开始之前,我们先讲下面向对象设计模式的六大原则
单一职责原则
就一个类而言,应该仅有一个引起它变化的原因。简单来说,一个类中应该是一组相关性很高的函数,数据的封装。
我们在App中往往会用到很多的公共方法,比如获取系统的时间,这个功能可能在App中的很多地方都要用到。这个时候我们一般会单独写个工具类,把处理时间相关的方法都放到这个类里面,这样就能减少重复代码,App的结构会更加清晰。当需要添加其他跟时间相关的方法时,就可以都加到这个TimeUtils类里面,这就是我们平时遵循的单一职责原则。
开闭原则
软件中的对象(类,模块,函数等),应该是对于扩展是开放的,对于修改是封闭的。
我们在软件开发过程中就要考虑到后续的扩展和修改。比如说,我们在开发一款类似于universal-image-loader的图片加载框架,可能一开始我们的功能比较简单,图片缓存只有内存缓存。当我们新版本需要添加SD卡缓存时,就要注意尽可能的减少对原来代码的修改,因为这样很可能会引入新的bug。而要做到开闭原则,一般有2中途径,一是通过继承原有的类;二是通过抽象和接口。
里式替换原则
所有引用基类的地方必须能透明的使用其子类。通俗的说,就是只要父类能出现的地方子类就可以出现,而且替换为子类以后不会出现任何错误或者异常。反过来就不行了,子类出现的地方父类不一定能适应。
要实现里氏替换原则,一般需要一个抽象的父类,父类中定义了子类的公共方法,子类继承或是实现父类以后扩展不同的功能,这样一来可以实现根据不同的需要来应用对应的子类,从而达到应用不同的功能的目的,程序的扩展性大大增强。同时这也体现了开闭原则,即当需要增加新功能时,只要继承或实现父类,实现新增的功能就达到了扩展的目的,而不是直接修改原来的代码,也即对扩展开放,对修改封闭。
依赖倒置原则
依赖倒置原则在Java中的表现就是:模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或者抽象类产生的。
一句话就是依赖抽象而不依赖具体的实现。比如上面我们说开发一个图片加载框架,那我们肯定会根据单一职责原则划分不同的模块,比如网络加载模块,图片缓存模块,以及主模块等。主模块肯定会调用图片缓存模块,如果我们调用的是图片缓存模块的具体实现,那么当我们修改图片模块时就很可能需要对应修改主模块,这就是耦合了,一个比较好的做法是将图片缓存模块抽象出来,而主模块调用这个抽象即可,这样也就是依赖抽象了。
接口隔离原则
类间的依赖关系应该建立在最小的接口上。
接口隔离原则就是让客户端依赖的接口尽可能的小。就是在上面提到的依赖倒置(依赖抽象而不是实现)原则的基础上,增加一个最小化依赖的原则。说白了就是在依赖接口的基础上依赖尽可能小的接口。
迪米特原则(又称最少知道原则)
一个对象应该对其他的对象有最小的了解。
通俗的讲,一个类应该对自己需要耦合或者调用的类知道得最小,调用者或者依赖者只要知道它需要的方法即可。要做到这个原则,需要我们对各个模块之间的功能进行很好的区分和分配,把相互之间的依赖和耦合减小到最小。
1.单例模式
所谓的单例设计指的是一个类只允许产生一个实例化对象。是最好理解的一种设计模式,分为懒汉式和饿汉式。
饿汉式:构造方法私有化,外部无法产生新的实例化对象,只能通过static方法获取到实例化对象。
public class Singleton {
//饿汉式
public static Singleton singleton=new Singleton();
//private 声明构造方法
private Singleton(){
}
public static Singleton getInstance(){
return singleton;
}
}
//Kotlin实现
object Singleton
线程安全的懒汉式:当第一次去使用Singleton对象的时候才会为其产生实例化对象的操作。
//Java实现
public class SingletonDemo {
private static SingletonDemo instance;
private SingletonDemo(){}
public static synchronized SingletonDemo getInstance(){//使用同步锁
if(instance==null){
instance=new SingletonDemo();
}
return instance;
}
}
//Kotlin实现
class SingletonDemo private constructor() {
companion object {
private var instance: SingletonDemo? = null
get() {
if (field == null) {
field = SingletonDemo()
}
return field
}
@Synchronized
fun get(): SingletonDemo{
return instance!!
}
}
}
大家都知道在使用懒汉式会出现线程安全的问题,需要使用使用同步锁,在Kotlin中,如果你需要将方法声明为同步,需要添加@Synchronized注解。
双重校验锁式:
public class LazySingleton {
//volatile保证了:1 instance在多线程并发的可见性
// 2 禁止instance在操作时的指令重排序
private static volatile LazySingleton instance=null;
private LazySingleton(){
}
public static LazySingleton getInstance(){
if(instance==null){ //第一次判空 保证不必要的同步
synchronized (LazySingleton.class){// synchronized对Singleton加全局锁,保证每次只要一个线程创建实例
if(instance==null){//第二次判空为了在null的情况下创建实列
instance=new LazySingleton();
}
}
}
return instance;
}
}
//kotlin实现
class LazySingleton private constructor() {
companion object {
val instance: LazySingleton by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
LazySingleton () }
}
}
当多个线程并发执行getInstance方法时,懒汉式会存在线程安全的问题,所以用到了synchronized来实现线程的同步,当一个线程获得锁的时候其他线程就只能再外等待其执行完毕。而饿汉式不存在线程安全的问题。
静态内部类
public class Singleton{
private static class SingletonHolder{
private staitc final Singleton INSTANCE=new Singleton();
}
private Singleton(){
}
public static Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
}
//kotlin实现
class Singleton private constructor() {
companion object {
val instance = SingletonHolder.holder
}
private object SingletonHolder {
val holder= Singleton()
}
}
相比饿汉式,这种方式即使加载了Singleton类,也不会创建Singleton实例,因为Singleton的静态变量放到了内部类中,只有内部类被加载了,Singleton实例才会被创建。
枚举
public enum Singleton{
INSTANCE;
public static Singleton getInstance(){
return INSTANCE;
}
}
反编译之后的代码:
public final class Singleton extends java.lang.Enum<Singleton>{
public staitc final Singleton INSTANCE;
staitc{
INSTANCE=new Singleton();
}
public static Singleton getInstance(){
return INSTANCE;
}
}
类加载的逻辑位于synchronized代码块中,是线程安全的,而饿汉式,静态内部类已经枚举方式实现的单例实例化都处于类加载时机,所以它们都是线程安全的。
2.工厂设计模式
工厂模式分为工厂方法模式和抽象工厂模式
工厂方法模式分为三种:
1.普通工厂模式,就是建立一个工厂类,对实现了同一接口的一些类进行实例的创建
2.多个工厂方法模式,是对普通工厂方法模式的改进,在普通工厂方法模式中,如果传递的字符串出错,则不能正确创建对象,而多个工厂方法模式是提供多个工厂方法,分别创建对象。
3.静态工厂方法模式,将上面的多个工厂方法模式的方法设置为静态的,不需要创建实例,直接调用即可。
2.1 普通工厂模式
建立一个工厂类,对实现了同一接口的一些类进行实例的创建。
public interface Sender {
void Send();
}
public class MailSender implements Sender {
@Override
public void Send() {
System.out.println("This is mail sender ...");
}
}
public class SmsSender implements Sender {
@Override
public void Send() {
System.out.println("This is sms sender ...");
}
}
public class FactoryPattern {
public static void main(String[] args) {
Sender sender=produce("mail");
}
public static Sender produce(String str){
if("mail".equals(str)){
return new MailSender();
}else if("sms".equals(str)){
return new SmsSender();
}else {
System.out.println("输入错误...");
return null;
}
}
}
2.2 多个工厂方法模式
该模式是对普通工厂方法模式的改进,在普通工厂方法模式中,如果传递的字符串出错,则不能正确创建对象,而多个工厂方法模式是提供多个工厂方法,分别创建对象。
interface Sender {
void Send();
}
class MailSender implements Sender {
@Override
public void Send() {
System.out.println("This is mail sender...");
}
}
class SmsSender implements Sender {
@Override
public void Send() {
System.out.println("This is sms sender...");
}
}
class SendFactory {
public Sender produceMail() {
return new MailSender();
}
public Sender produceSms() {
return new SmsSender();
}
}
public class FactoryPattern {
public static void main(String[] args) {
SendFactory factory = new SendFactory();
Sender sender = factory.produceMail();
sender.Send();
}
}
2.3 静态工厂方法模式
将上面的多个工厂方法模式里的方法置为静态的,不需要创建实例,直接调用即可。
interface Sender {
void Send();
}
class MailSender implements Sender {
@Override
public void Send() {
System.out.println("This is mail sender...");
}
}
class SmsSender implements Sender {
@Override
public void Send() {
System.out.println("This is sms sender...");
}
}
class SendFactory {
public static Sender produceMail() {
return new MailSender();
}
public static Sender produceSms() {
return new SmsSender();
}
}
public class FactoryPattern {
public static void main(String[] args) {
Sender sender = SendFactory.produceMail();
sender.Send();
}
}
2.4 抽象工厂模式
工厂方法模式有一个问题就是,类的创建依赖工厂类,也就是说,如果想要扩展程序,必须对工厂类进行修改,这违背了闭包原则,所以,从设计角度考虑,有一定的问题,如何解决?
那么这就用到了抽象工厂模式,创建多个工厂类,这样一旦需要新增新的功能,直接增加新的工厂类就可以了,不需要修改之前的代码。
public interface Sender {
void send();
}
public interface Provider {
Sender produce();
}
public class MailSender implements Sender {
@Override
public void send() {
System.out.println("This is mail sender...");
}
}
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();
}
}
3.适配器模式
适配器模式是将某个类的接口转换成客户端期望的另一个接口表示,目的是消除由于接口不匹配所造成的类的兼容性问题。主要分三类:类的适配器模式,对象的适配器模式,接口的适配器模式。
3.1 类的适配器模式:
public class Source {
public void method1(){
System.out.println("This is origin method");
}
}
public interface Target {
/**
* 与原类中的方法相同
*/
public void method1();
/**
* 新类的方法
*/
public void mehtod2();
}
public class Adapter extends Source implements Target {
@Override
public void mehtod2() {
System.out.println("This is the target method...");
}
}
public class AdapterPattern {
public static void main(String[] args) {
Target target=new Adapter();
target.method1();
target.mehtod2();
}
}
3.2 对象的适配器模式
基本思路和类的适配器模式相同,只是将Adapter类作修改,这次不继承Source类,而是持有Source类的实例,以达到解决兼容性的问题。
class Source {
public void method1() {
System.out.println("This is original method...");
}
}
interface Targetable {
/**
* 与原类中的方法相同
*/
public void method1();
/**
* 新类的方法
*/
public void method2();
}
class Wrapper implements Targetable {
private Source source;
public Wrapper(Source source) {
super();
this.source = source;
}
@Override
public void method1() {
source.method1();
}
@Override
public void method2() {
System.out.println("This is the targetable method...");
}
}
public class AdapterPattern {
public static void main(String[] args) {
Source source = new Source();
Targetable targetable = new Wrapper(source);
targetable.method1();
targetable.method2();
}
}
3.3 接口的适配器模式
接口的适配器是这样的:有时我们写的一个接口中有多个抽象方法,当我们写该接口的实现类时,必须实现该接口的所有方法,这明显有时比较浪费,因为并不是所有的方法都是我们需要的,有时只需要某一些,此处为了解决这个问题,我们引入了接口的适配器模式,借助于一个抽象类,该抽象类实现了该接口,实现了所有的方法,而我们不和原始的接口打交道,只和该抽象类取得联系,所以我们写一个类,继承该抽象类,重写我们需要的方法就行。
/**
* 定义端口接口 ,提供通信服务
*/
public interface Port {
/**
* 远程SSH端口为22
*/
void SSH();
/**
* 网络端口80
*/
void NET();
/**
* Tomcat容器8080
*/
void Tomcat();
/***
* MySQL 数据库端口为3306
*/
void MySQL();
}
public abstract class Wrapper implements Port{
@Override
public void SSH() {
}
@Override
public void NET() {
}
@Override
public void Tomcat() {
}
@Override
public void MySQL() {
}
}
public class Chat extends Wrapper {
@Override
public void NET() {
System.out.println("Hello World...");
}
}
public class Server extends Wrapper {
@Override
public void SSH() {
super.SSH();
System.out.println("Connect success");
}
@Override
public void NET() {
super.NET();
System.out.println("WWW...");
}
@Override
public void Tomcat() {
super.Tomcat();
System.out.println("Tomcat is running...");
}
@Override
public void MySQL() {
super.MySQL();
System.out.println("MySQL is running...");
}
}
public class AdapterPattern {
static Port chatPort=new Chat();
static Port serverPort=new Server();
public static void main(String[] args) {
chatPort.NET();
serverPort.SSH();
serverPort.NET();
serverPort.Tomcat();
serverPort.MySQL();
}
}
4.装饰模式
就是给一个对象增加一些新的功能,而且是动态的,要求装饰对象和被装饰对象实现同一个接口,装饰对象持有被装饰对象的实例。
就增加功能来说,装饰模式比生成子类更为灵活。
public interface Shape {
void draw();
}
/**
* 实现接口的实体类
*/
public class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("Shap:Rectangle...");
}
}
public class Circle implements Shape {
@Override
public void draw() {
System.out.println("Shap:Circle...");
}
}
/**
* 创建实现了Shape接口的抽象装饰类
*/
public abstract class ShapDecorator implements Shape {
protected Shape decoratedShap;
public ShapDecorator(Shape decoratedShap){
this.decoratedShap=decoratedShap;
}
@Override
public void draw() {
decoratedShap.draw();
}
}
/***
* 创建扩展自ShapeDecorator类的实体装饰类
*/
public class RedShapDecorator extends ShapDecorator {
public RedShapDecorator(Shape decoratedShap) {
super(decoratedShap);
}
@Override
public void draw() {
super.draw();
setReeBorder();
}
private void setReeBorder(){
System.out.println("Border Color:Red");
}
}
public class DecoratorPattern {
public static void main(String[] args) {
Shape circle=new Circle();
Shape redCircle=new RedShapDecorator(new Circle());
Shape redRectangle=new RedShapDecorator(new Rectangle());
}
}
5.享元模式
简单描述一下就是一批对象中既有相同的内容也有不同的内容,相同的内容采用共享的方式,不同的内容通过动态传递的方式,来尽量减少对象的产生。
使用场景
最常见的一个场景就是Java JDK里面的String字符串类,因为JVM中有常量池,常量池的实现就是一种享元模式,避免多个相同对象的存在。另外线程池以及很多用到缓冲池的地方也采用了享元模式,比如Integer类中默认缓存了-128-127之间的整数。等
public interface IFlyweight{
void setDetail(int width,int height);
}
public class FlyweightImpl implements IFlyweighet{
private Color color;
private int width;
private int height;
@Override
public void setDetail(int width,int height){
this.width=width;
this.height=height;
System.out.println(toString());
}
public FlyweightImpl(Color color){
this.color=color;
}
}
public class FlyweightFactory{
private Map<String,FlywieightImpl> colorFlyweightMap=new HashMap<>()
//根据名称获取对象
public FlyweightImpl getFlyweight(String colorName){
if(colorflyweightMap.containsKey(colorName)){
return colorFlyweightMap.get(colorName)
}else{
Color color=new Color(colorName);
FlyweightImpl impl=new FlyweightImpl(color);
colorFlyweightMap.put(colorName,impl);
return impl;
}
}
}
//Color类
public class Color{
private String type;
public Color(String type){
this.type=type;
}
}
public static void main(String[] args){
FlyweightFactory factory=new FlyweightFactory();
IFlyweight flyweight0=factory.getFlyweight("red");
flyweight0.setDetail(100,200);
IFlyweight flyweight1=factory.getFlyweight("red");
flyweight1.setDetail(300,400);
IFlyweight flyweight2=factory.getFlyweight("green");
flyweight2.setDetail(500,600);
System.out.println(flyweight0==flyweight1)
System.out.println(flyweight0==flyweight2)
}
上面测试类中,我们分别定义了两种颜色的三个对象,由于相同颜色的对象已经被缓存了,所以我们输出的结果如下
true
false
从上面的运行结果中,我们可以看出,flyweight0 与flyweight1 其实是同一个对象,虽然他们的 width 和 height 属性值不一样,但是他们的底层对应的是同一个对象本身。这就是享元模式的核心内容,通过共享变量,来减少对象的产生,从而减少内存的使用。