Java之设计模式

文章目录

一、 设计模式优点

  • 提高代码复用率,降低开发成本和周期
  • 提高代码可维护性、可拓展性
  • 使代码更加优雅
  • 让代码更容易被他人理解

二、设计模式七大原则

1、单一职责原则

如果一个类承担的职责过多,即耦合性太高=一个职责的变化可能会影响到其他的职责
所以要求单一职责原则:一个类=只有一个引起它变化的原因。(只负责担任一个职责)

2、开放封闭原则

1、即每次发生变化时,要通过添加新的代码来增强现有类型的行为,而不是修改原有的代码。
2、符合开放封闭原则的最好方式是提供一个固有的接口,然后让所有可能发生变化的类实现该接口,让固定的接口与相关对象进行交互。

3、 里氏代替原则

1、在软件开发过程中,子类替换父类后,程序的行为是一样的。
2、只有当子类替换掉父类后软件的功能不受影响时,父类才能真正地被复用,而子类也可以在父类的基础上添加新的行为。

4、依赖倒置原则

通过抽象(接口或抽象类)使各个类或模块实现彼此独立,互不影响,实现模块间的松耦合。

5、接口隔离原则

不要让一个单一的接口承担过多的职责,而应把每个职责分离到多个专门的接口中,进行接口分离。

6、合成复用原则

新对象通过向这些对象的委派达到复用已用功能的目的。简单地说,就是要尽量使用合成/聚合,尽量不要使用继承。

7、最少知识原则(迪米特法则)

一个模块或对象应尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立,这样当一个模块修改时,影响的模块就会越少,扩展起来更加容易。 (外观模式)

三、设计模式分类(23种)

1、创建型

特点
1、封装了具体类的信息
2、隐藏了类的实例化过程

3.1.1 单例模式模式
1、作用

保证1个类只有1个对象,降低对象之间的耦合度。其他对象访问的都是该类同一个实例。

2、工作原理
  • 单例类只能有一个实例:创建静态私有变量 ourInstance 为Singleton 的唯一实例
  • 单例类必须自己创建自己的唯一实例:把类的构造方法私有化,内部进行实例化,不让外部调用构造方法实例化
  • 单例类必须给所有其他对象提供这一实例:定义共有方法提供该类全局唯一访问点,外部通过调用getInstance()方法来返回唯一实例
 public class Singleton {
//1. 创建私有变量 ourInstance(用以记录 Singleton 的唯一实例)
//2. 内部进行实例化
    private static Singleton ourInstance  = new  Singleton();

//3. 把类的构造方法私有化,不让外部调用构造方法实例化
    private Singleton() {
    }
//4. 定义公有方法提供该类的全局唯一访问点
//5. 外部通过调用getInstance()方法来返回唯一的实例
    public static  Singleton newInstance() {
        return ourInstance;
    }
}
3、单例分类

在这里插入图片描述

4、饿汉式与懒汉式区别
**单例创建的时机**
饿汉式:单例创建时机不可控,即类加载时 自动创建 单例
懒汉式:单例创建时机可控,即有需要时,才手动创建 单例
**单例是否线程安全**
饿汉式是线程安全的,在多线程下使用,因为JVM值加载一次单例类。
懒汉式是线程不安全的,在多线程下不使用。因为可能存在多个线程并发调用new Instance(),从而重复创建单例对象。
如:线程A执行到singleton = new Singleton(),但还没获取对象(对象初始化需要时间)。此时线程B也在执行,执行到if(singleton==null
判断为真,预示继续运行创建单例对象。最终线程A、B分别同时获得了一个单例对象,在内存中出现两个单例类的对象。
5、单例模式的发展

1、从线程安全的饿汉式,但是饿汉式在类加载时就把实例对象创建出来了,
2、为了减少性能消耗要求我们在使用时再创建单例,则出现了懒汉式(什么时候需要再创建),
3、但是懒汉式是线程不安全的,为了实现线程安全,首先使用同步锁 synchronized锁住 创建单例getInstance()的方法,防止多个线程同时使用,避免单例被多次创建。
4、此方法是同步了getInstance()方法,所以每次创建都需要经过同步锁,造成了性能浪费,发展成双重锁校验方式: 在同步锁基础上添加if 判断,如果单例实例已经存在,那么不需要经过同步锁,直接返回单例的实例。

6、单例实现方式
饿汉式
class Singleton {

    // 1. 加载该类时,单例就会自动被创建
    private static  Singleton ourInstance  = new  Singleton();
    
    // 2. 构造函数 设置为 私有权限
    // 原因:禁止他人创建实例 
    private Singleton() {
    }
    
    // 3. 通过调用静态方法获得创建的单例
    public static  Singleton newInstance() {
        return ourInstance;
    }
}
懒汉式
class Singleton {
    // 1. 类加载时,先不自动创建单例
   //  即,将单例的引用先赋值为 Null
    private static  Singleton ourInstance  = null;

    // 2. 构造函数 设置为 私有权限
    // 原因:禁止他人创建实例 
    private Singleton() {
    }
    
    // 3. 需要时才手动调用 newInstance() 创建 单例   
    public static  Singleton newInstance() {
    // 先判断单例是否为空,以避免重复创建
    if( ourInstance == null){
        ourInstance = new Singleton();
        }
        return ourInstance;
    }
}
双重校验锁
class Singleton {
    private static  Singleton ourInstance  = null;

    private Singleton() {
    }
    
    public static  Singleton newInstance() {
     // 加入双重校验锁
    // 校验锁1:第1个if
    if( ourInstance == null){  // ①
     synchronized (Singleton.class){ // ②
      // 校验锁2:第2个 if
      if( ourInstance == null){
          ourInstance = new Singleton();
          }
      }
  }
        return ourInstance;
   }
}

// 说明
// 校验锁1:第1个if
// 作用:若单例已创建,则直接返回已创建的单例,无需再执行加锁操作
// 即直接跳到执行 return ourInstance

// 校验锁2:第2个 if 
// 作用:防止多次创建单例问题
// 原理
  // 1. 线程A调用newInstance(),当运行到②位置时,此时线程B也调用了newInstance()
  // 2. 因线程A并没有执行instance = new Singleton();,此时instance仍为空,因此线程B能突破第1层 if 判断,运行到①位置等待synchronized中的A线程执行完毕
  // 3. 当线程A释放同步锁时,单例已创建,即instance已非空
  // 4. 此时线程B 从①开始执行到位置②。此时第2层 if 判断 = 为空(单例已创建),因此也不会创建多余的实例
静态内部类
class Singleton {
    
    // 1. 创建静态内部类
    private static class Singleton2 {
       // 在静态内部类里创建单例
      private static  Singleton ourInstance  = new Singleton();
    }

    // 私有构造函数
    private Singleton() {
    }
    
    // 延迟加载、按需创建
    public static  Singleton newInstance() {
        return Singleton2.ourInstance;
    }

}

// 调用过程说明:
      // 1. 外部调用类的newInstance() 
      // 2. 自动调用Singleton2.ourInstance
       // 2.1 此时单例类Singleton2得到初始化
       // 2.2 而该类在装载 & 被初始化时,会初始化它的静态域,从而创建单例;
       // 2.3 由于是静态域,因此只会JVM只会加载1遍,Java虚拟机保证了线程安全性
      // 3. 最终只创建1个单例
枚举方式
/**
 * Title:Singleton<br>
 * Description:单例模式——枚举方式
 * 
 * @author QiuChangjin
 * @date 2018年4月17日
 */
public class Singleton {
    public static void main(String[] args) {
        Single single = Single.SINGLE;
    }

    enum Single {
        SINGLE;

        private Single() {
        }
    }
}
3.1.2 简单工厂模式
定义:

将“类实例化的操作”与“使用对象的操作”分开,让使用者不用知道具体参数就可以实例化出所需要的“产品”类,从而避免了在客户端代码中显式指定,实现了解耦。
即使用者可直接消费产品而不需要知道其生产(实例化)的细节。

优点
  • 将创建实例的工作与使用实例的工作分开,使用者不必关心类对象如何创建,实现了解耦;
  • 把初始化实例时的工作放到工厂里进行,使代码更容易维护。 更符合面向对象的原则 & 面向接口编程,而不是面向实现编程。
缺点
  • 工厂类集中了所有实例(产品)的创建逻辑,一旦这个工厂不能正常工作,整个系统都会受到影响;
  • 违背“开放 - 关闭原则”,一旦添加新产品就不得不修改工厂类的逻辑,这样就会造成工厂逻辑过于复杂
使用步骤

1、创建抽象产品类 & 定义具体产品的公共接口;
2、创建具体产品类(继承抽象产品类) & 定义生产的具体产品;
3、创建工厂类,通过创建静态方法根据传入不同参数从而创建不同具体产品类的实例;
4、外界通过调用工厂类的静态方法,传入不同参数从而创建不同具体产品类的实例

示例

步骤1. 创建抽象产品类,定义具体产品的公共接口

abstract class Product{
    public abstract void Show();
}

步骤2. 创建具体产品类(继承抽象产品类),定义生产的具体产品

//具体产品类A
class  ProductA extends  Product{

    @Override
    public void Show() {
        System.out.println("生产出了产品A");
    }
}

//具体产品类B
class  ProductB extends  Product{

    @Override
    public void Show() {
        System.out.println("生产出了产品C");
    }
}

//具体产品类C
class  ProductC extends  Product{

    @Override
    public void Show() {
        System.out.println("生产出了产品C");
    }
}

步骤3. 创建工厂类,通过创建静态方法从而根据传入不同参数创建不同具体产品类的实例

class  Factory {
    public static Product Manufacture(String ProductName){
//工厂类里用switch语句控制生产哪种商品;
//使用者只需要调用工厂类的静态方法就可以实现产品类的实例化。
        switch (ProductName){
            case "A":
                return new ProductA();

            case "B":
                return new ProductB();

            case "C":
                return new ProductC();

            default:
                return null;

        }
    }
}

步骤4. 外界通过调用工厂类的静态方法,传入不同参数从而创建不同具体产品类的实例

//工厂产品生产流程
public class SimpleFactoryPattern {
    public static void main(String[] args){
        Factory mFactory = new Factory();

        //客户要产品A
        try {
//调用工厂类的静态方法 & 传入不同参数从而创建产品实例
            mFactory.Manufacture("A").Show();
        }catch (NullPointerException e){
            System.out.println("没有这一类产品");
        }

        //客户要产品B
        try {
            mFactory.Manufacture("B").Show();
        }catch (NullPointerException e){
            System.out.println("没有这一类产品");
        }

        //客户要产品C
        try {
            mFactory.Manufacture("C").Show();
        }catch (NullPointerException e){
            System.out.println("没有这一类产品");
        }

        //客户要产品D
        try {
            mFactory.Manufacture("D").Show();
        }catch (NullPointerException e){
            System.out.println("没有这一类产品");
        }
    }
}
3.1.3 工厂方法模式
定义

工厂方法模式可理解为多态工厂模式,通过定义工厂父类负责定义创建对象的公共接口,而子类则负责生成具体的对象。将类的实例化(具体产品的创建)延迟到工厂类的子类(具体工厂)中完成,即由子类来决定该实例化(创建)哪一个类。

  • 解决了简单工厂的缺点:工厂一旦要生产新产品就需要修改工厂类的方法逻辑,违背了"开放-关闭"原则

  • 之所以可以解决简单工厂的问题,是因为工厂方法模式把具体产品的创建推迟到工厂类的子类(具体工厂)中,此时工厂类不再负责所有产品的创建,而只是给出具体工厂必须实现的接口,这样工厂方法模式在添加新产品的时候就不修改工厂类逻辑而是添加新的工厂子类,符合开放封闭原则,克服了简单工厂模式中缺点

优点

1、更符合开-闭原则
新增一种产品时,只需要增加相应的具体产品类和相应的工厂子类即可(简单工厂模式需要修改工厂类的判断逻辑)
2、符合单一职责原则
每个具体工厂类只负责创建对应的产品(简单工厂中的工厂类存在复杂的switch逻辑判断

缺点

1、添加新产品时,除了增加新产品类外,还要提供与之对应的具体工厂类,系统类的个数将成对增加,在一定程度上增加了系统的复杂度;
2、有更多的类需要编译和运行,会给系统带来一些额外的开销;
3、一个具体工厂只能创建一种具体产品

示例:

概况:
背景:有一间塑料加工厂(仅生产A类产品);随着客户需求的变化,客户需要生产B类产品
冲突:改变原有塑料加工厂的配置和变化非常困难,假设下一次客户需要再发生变化,再次改变将增大非常大的成本
方案:置办塑料分厂B来生产B类产品

步骤1: 创建抽象工厂类,定义具体工厂的公共接口

abstract class Factory{
    public abstract Product Manufacture();
}

步骤2: 创建抽象产品类 ,定义具体产品的公共接口;

abstract class Product{
    public abstract void Show();
}

步骤3: 创建具体产品类(继承抽象产品类), 定义生产的具体产品;

//具体产品A类
class  ProductA extends  Product{
    @Override
    public void Show() {
        System.out.println("生产出了产品A");
    }
}

//具体产品B类
class  ProductB extends  Product{

    @Override
    public void Show() {
        System.out.println("生产出了产品B");
    }
}

步骤4:创建具体工厂类(继承抽象工厂类),定义创建对应具体产品实例的方法;

//工厂A类 - 生产A类产品
class  FactoryA extends Factory{
    @Override
    public Product Manufacture() {
        return new ProductA();
    }
}

//工厂B类 - 生产B类产品
class  FactoryB extends Factory{
    @Override
    public Product Manufacture() {
        return new ProductB();
    }
}

步骤5:外界通过调用具体工厂类的方法,从而创建不同具体产品类的实例

//生产工作流程
public class FactoryPattern {
    public static void main(String[] args){
        //客户要产品A
        FactoryA mFactoryA = new FactoryA();
        mFactoryA.Manufacture().Show();

        //客户要产品B
        FactoryB mFactoryB = new FactoryB();
        mFactoryB.Manufacture().Show();
    }
}
3.1.4 抽象工厂模式
定义

抽象工厂模式与工厂方法模式最大的区别:抽象工厂中每个工厂可以创建多种类的产品;而工厂方法每个工厂只能创建一类。
允许使用抽象的接口来创建一组相关产品,而不需要知道或关心实际生产出的具体产品是什么,这样就可以从具体产品中被解耦。
可解决工厂模式缺点:每个工厂只能创建一类产品。

优点

1、降低耦合
抽象工厂模式将具体产品的创建延迟到具体工厂的子类中,这样将对象的创建封装起来,可以减少客户端与具体产品类之间的依赖,从而使系统耦合度低,这样更有利于后期的维护和扩展;
2、更符合开-闭原则
新增一种产品类时,只需要增加相应的具体产品类和相应的工厂子类即可。简单工厂模式需要修改工厂类的判断逻辑
3、符合单一职责原则
每个具体工厂类只负责创建对应的产品,简单工厂中的工厂类存在复杂的switch逻辑判断
4、不使用静态工厂方法,可以形成基于继承的等级结构。简单工厂模式的工厂类使用静态工厂方法

缺点

抽象工厂模式很难支持新种类产品的变化。
这是因为抽象工厂接口中已经确定了可以被创建的产品集合,如果需要添加新产品,此时就必须去修改抽象工厂的接口,这样就涉及到抽象工厂类的以及所有子类的改变,这样也就违背了“开发——封闭”原则。

示例

概况
背景:有两间塑料加工厂(A厂仅生产容器类产品;B厂仅生产模具类产品);随着客户需求的变化,A厂所在地的客户需要也模具类产品,B厂所在地的客户也需要容器类产品;
冲突:没有资源(资金+租位)在当地分别开设多一家注塑分厂
方案:在原有的两家塑料厂里增设生产需求的功能,即A厂能生产容器+模具产品;B厂间能生产容器+模具产品。

步骤1: 创建抽象工厂类,定义具体工厂的公共接口

abstract class Factory{
   public abstract Product ManufactureContainer();
    public abstract Product ManufactureMould();
}

步骤2: 创建抽象产品族类 ,定义具体产品的公共接口;

abstract class AbstractProduct{
    public abstract void Show();
}

步骤3: 创建抽象产品类 ,定义具体产品的公共接口;

//容器产品抽象类
abstract class ContainerProduct extends AbstractProduct{
    @Override
    public abstract void Show();
}

//模具产品抽象类
abstract class MouldProduct extends AbstractProduct{
    @Override
    public abstract void Show();
}

步骤4: 创建具体产品类(继承抽象产品类), 定义生产的具体产品;

//容器产品A类
class ContainerProductA extends ContainerProduct{
    @Override
    public void Show() {
        System.out.println("生产出了容器产品A");
    }
}

//容器产品B类
class ContainerProductB extends ContainerProduct{
    @Override
    public void Show() {
        System.out.println("生产出了容器产品B");
    }
}

//模具产品A类
class MouldProductA extends MouldProduct{

    @Override
    public void Show() {
        System.out.println("生产出了模具产品A");
    }
}

//模具产品B类
class MouldProductB extends MouldProduct{

    @Override
    public void Show() {
        System.out.println("生产出了模具产品B");
    }
}

步骤5:创建具体工厂类(继承抽象工厂类),定义创建对应具体产品实例的方法;

//A厂 - 生产模具+容器产品
class FactoryA extends Factory{

    @Override
    public Product ManufactureContainer() {
        return new ContainerProductA();
    }

    @Override
    public Product ManufactureMould() {
        return new MouldProductA();
    }
}

//B厂 - 生产模具+容器产品
class FactoryB extends Factory{

    @Override
    public Product ManufactureContainer() {
        return new ContainerProductB();
    }

    @Override
    public Product ManufactureMould() {
        return new MouldProductB();
    }
}

步骤6:客户端通过实例化具体的工厂类,并调用其创建不同目标产品的方法创建不同具体产品类的实例

//生产工作流程
public class AbstractFactoryPattern {
    public static void main(String[] args){
        FactoryA mFactoryA = new FactoryA();
        FactoryB mFactoryB = new FactoryB();
        //A厂当地客户需要容器产品A
        mFactoryA.ManufactureContainer().Show();
        //A厂当地客户需要模具产品A
        mFactoryA.ManufactureMould().Show();

        //B厂当地客户需要容器产品B
        mFactoryB.ManufactureContainer().Show();
        //B厂当地客户需要模具产品B
        mFactoryB.ManufactureMould().Show();

    }
}
简单工厂模式、工厂模式、抽象工厂模式总结
  • 简单工厂 : 用来生产同一等级结构中的任意产品。(不支持拓展增加产品)
  • 工厂方法 :用来生产同一等级结构中的固定产品。(支持拓展增加产品)
  • 抽象工厂 :用来生产不同产品族的全部产品。(不支持拓展增加产品;支持增加产品族)
3.1.5 建造者模式
定义

即Builder模式可以将一个类的构建和表示进行分离,使得同样的构建过程可以创建不同的表示。

Android中应用

AlertDialog
universal-image-loader
OkHttp中HttpClient和Request建造
Retrofit中Client和Request建造

ImageLoader示例

未采用Builder模式的ImageLoader

public class ImageLoader {
    //图片加载配置
    private int loadingImageId;
    private int loadingFailImageId;

    // 图片缓存,依赖接口
    ImageCache mImageCache = new MemoryCache();

    // 线程池,线程数量为CPU的数量
    ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

    //省略单例模式实现
    
    /**
     * 设置图片缓存
     * @param cache
     */
    public void setImageCache(ImageCache cache) {
        mImageCache = cache;
    }

    /**
     * 设置图片加载中显示的图片
     * @param resId
     */
    public Builder setLoadingPlaceholder(int resId) {
        loadingImageId = resId;
    }

    /**
     * 设置加载失败显示的图片
     * @param resId
     */
    public Builder setLoadingFailPlaceholder(int resId) {
        loadingFailImageId = resId;
    }
    
    /**
     * 显示图片
     * @param imageUrl
     * @param imageView
     */
    public void displayImage(String imageUrl, ImageView imageView) {
        Bitmap bitmap = mImageCache.get(imageUrl);
        if (bitmap != null) {
            imageView.setImageBitmap(bitmap);
            return;
        }
        // 图片没有缓存,提交到线程池下载
        submitLoadRequest(imageUrl, imageView);
    }

    /**
     * 下载图片
     * @param imageUrl
     * @param imageView
     */
    private void submitLoadRequest(final String imageUrl, final ImageView imageView) {
        imageView.setImageResource(loadingImageId);
        imageView.setTag(imageUrl);
        mExecutorService.submit(new Runnable() {
            @Override
            public void run() {
                Bitmap bitmap = downloadImage(imageUrl);
                if (bitmap == null) {
                    imageView.setImageResource(loadingFailImageId);
                    return;
                }
                if (imageUrl.equals(imageView.getTag())) {
                    imageView.setImageBitmap(bitmap);
                }
                mImageCache.put(imageUrl, bitmap);
            }
        });
    }

    /**
     * 下载图片
     * @param imageUrl
     * @return
     */
    private Bitmap downloadImage(String imageUrl) {
        Bitmap bitmap = null;
        //省略下载部分代码
        return bitmap;
    }
}

从上面的代码中我们可以看出,每当需要增加一个设置选项的时候,就需要修改ImageLoader的代码,违背了开闭原则,而且ImageLoader中的代码会越来越多,不利于维护

Builder模式的ImageLoader
首先是把ImageLoader的设置都放在单独的配置类里,每个set方法都返回this,从而达到链式调用的目的

public class ImageLoaderConfig {
    // 图片缓存,依赖接口
    public ImageCache mImageCache = new MemoryCache();

    //加载图片时的loading和加载失败的图片配置对象
    public DisplayConfig displayConfig = new DisplayConfig();

    //线程数量,默认为CPU数量+1;
    public int threadCount = Runtime.getRuntime().availableProcessors() + 1;

    private ImageLoaderConfig() {
    }

    /**
     * 配置类的Builder
     */
    public static class Builder {
        // 图片缓存,依赖接口
        ImageCache mImageCache = new MemoryCache();

        //加载图片时的loading和加载失败的图片配置对象
        DisplayConfig displayConfig = new DisplayConfig();

        //线程数量,默认为CPU数量+1;
        int threadCount = Runtime.getRuntime().availableProcessors() + 1;

        /**
         * 设置线程数量
         * @param count
         * @return
         */
        public Builder setThreadCount(int count) {
            threadCount = Math.max(1, count);
            return this;
        }

        /**
         * 设置图片缓存
         * @param cache
         * @return
         */
        public Builder setImageCache(ImageCache cache) {
            mImageCache = cache;
            return this;
        }

        /**
         * 设置图片加载中显示的图片
         * @param resId
         * @return
         */
        public Builder setLoadingPlaceholder(int resId) {
            displayConfig.loadingImageId = resId;
            return this;
        }

        /**
         * 设置加载失败显示的图片
         * @param resId
         * @return
         */
        public Builder setLoadingFailPlaceholder(int resId) {
            displayConfig.loadingFailImageId = resId;
            return this;
        }

        void applyConfig(ImageLoaderConfig config) {
            config.displayConfig = this.displayConfig;
            config.mImageCache = this.mImageCache;
            config.threadCount = this.threadCount;
        }

        /**
         * 根据已经设置好的属性创建配置对象
         * @return
         */
        public ImageLoaderConfig create() {
            ImageLoaderConfig config = new ImageLoaderConfig();
            applyConfig(config);
            return config;
        }
    }
}

ImageLoader的修改

public class ImageLoader {
    //图片加载配置
    ImageLoaderConfig mConfig;

    // 图片缓存,依赖接口
    ImageCache mImageCache = new MemoryCache();

    // 线程池,线程数量为CPU的数量
    ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

    //省略单例模式实现
    
    //初始化ImageLoader
    public void init(ImageLoaderConfig config) {
        mConfig = config;
        mImageCache = mConfig.mImageCache;
    }
    
    /**
     * 显示图片
     * @param imageUrl
     * @param imageView
     */
    public void displayImage(String imageUrl, ImageView imageView) {
        Bitmap bitmap = mImageCache.get(imageUrl);
        if (bitmap != null) {
            imageView.setImageBitmap(bitmap);
            return;
        }
        // 图片没有缓存,提交到线程池下载
        submitLoadRequest(imageUrl, imageView);
    }

    /**
     * 下载图片
     * @param imageUrl
     * @param imageView
     */
    private void submitLoadRequest(final String imageUrl, final ImageView imageView) {
        imageView.setImageResource(mConfig.displayConfig.loadingImageId);
        imageView.setTag(imageUrl);
        mExecutorService.submit(new Runnable() {
            @Override
            public void run() {
                Bitmap bitmap = downloadImage(imageUrl);
                if (bitmap == null) {
                    imageView.setImageResource(mConfig.displayConfig.loadingFailImageId);
                    return;
                }
                if (imageUrl.equals(imageView.getTag())) {
                    imageView.setImageBitmap(bitmap);
                }
                mImageCache.put(imageUrl, bitmap);
            }
        });
    }

    /**
     * 下载图片
     * @param imageUrl
     * @return
     */
    private Bitmap downloadImage(String imageUrl) {
        Bitmap bitmap = null;
        //省略下载部分代码
        return bitmap;
    }
}

调用形式

ImageLoaderConfig config = new ImageLoaderConfig.Builder()
        .setImageCache(new MemoryCache())
        .setThreadCount(2)
        .setLoadingFailPlaceholder(R.drawable.loading_fail)
        .setLoadingPlaceholder(R.drawable.loading)
        .create();
ImageLoader.getInstance().init(config);
3.1.6 原型模式
定义及作用

这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。
例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。

深拷贝与浅拷贝区别

浅拷贝又叫影子拷贝,例在拷贝文档时并没有把原文档中的字段都重新构造了一遍,而只是拷贝了引用,也就是副文档的字段引用原始文档的字段,这样的话修改副文档中的内容就会连原始文档也改掉了,这就是浅拷贝

深拷贝就是在浅拷贝的基础上,对于引用类型的字段也要采用拷贝的形式,比如上面的images,而像String、int这些基本数据类型则没关系

建造者模式,抽象工厂模式区别

建造者模式与工厂模式

类似点:

  • 都是用来创建一个对象,并且用户不需要去关心实际的创建过程

不同点:

  • 建造者模式着重于对部件的组装来完成对象的创建,一步步的去创建对象;工厂模式直接返回要创建的对象,无需关心各部分是如何创建出来的
  • 工厂模式返回不同的汽车对象是通过接口继承,而建造者模式则是通过组成部分不同来创建不同的对象。建造者模式粒度更加细

2、结构型

3.2.1 适配器模式
简介

适配器模式把一种接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。

优点
  • 更好的复用性
    系统需要使用现有的类,而此类的接口不符合系统的需要。那么通过适配器模式就可以让这些功能得到更好的复用。
  • 透明、简单
    客户端可以调用同一接口,因而对客户端来说是透明的。这样做更简单 & 更直接
  • 更好的扩展性
    在实现适配器功能的时候,可以调用自己开发的功能,从而自然地扩展系统的功能。
  • 解耦性
    将目标类和适配者类解耦,通过引入一个适配器类重用现有的适配者类,而无需修改原有代码
  • 符合开放-关闭原则
    同一个适配器可以把适配者类和它的子类都适配到目标接口;可以为不同的目标接口实现不同的适配器,而不需要修改待适配类
缺点
  • 过多的使用适配器,会让系统非常零乱,不易整体进行把握
使用示例

步骤1:创建Target接口

public interface Target {
 
    //这是源类Adapteee没有的方法
    public void Request(); 
}

步骤2:创建源类(Adaptee)

public class Adaptee {
    
    public void SpecificRequest(){
    }
}

步骤3:创建适配器类(Adapter)

class Adapter implements Target{  
    // 直接关联被适配类  
    private Adaptee adaptee;  
    
    // 可以通过构造函数传入具体需要适配的被适配类对象  
    public Adapter (Adaptee adaptee) {  
        this.adaptee = adaptee;  
    }  
    
    @Override
    public void Request() {  
        // 这里是使用委托的方式完成特殊功能  
        this.adaptee.SpecificRequest();  
    }  
}

步骤4:定义具体使用目标类,并通过Adapter类调用所需要的方法从而实现目标

public class AdapterPattern {
    public static void main(String[] args){
        //需要先创建一个被适配类的对象作为参数  
        Target mAdapter = new Adapter(new Adaptee());
        mAdapter.Request();
    }
}
Android应用场景

最常见的ListView、GridView、RecyclerView等的Adapter。

ListView通过引入Adapter适配器类把那些多变的布局和数据交给用户处理,然后通过适配器中的接口获取需要的数据来完成自己的功能,从而达到了很好的灵活性。这里面最重要的接口莫过于getView()接口了,该接口返回一个View对象,而千变万化的UI视图都是View的子类,通过这样一种处理就将子View的变化隔离了,保证了AbsListView类族的高度可定制化。

具体分析看ListView,RecyclerView控件分析

3.2.2 代理模式

1、代理概念

为其他对象提供一种代理以控制对这个对象的访问。代理类和委托类有共同的父类或父接口,这样在任何使用委托类对象的地方都可以用代理对象替代。代理对象起到中介的作用,可去掉功能服务或增加额外的服务。负责为委托类预处理消息,过滤消息并将请求分派给委托类处理,以及进行消息被委托类执行后的后续操作。

2、代理模型

代理模式一般设计到角色有4 种:
1、抽象角色:对应代理接口(<< interface >>Subject),用来定义代理类和委托类的公共对外方法/接口;
2、真实角色:对应委托类(接口实现类RealSubject),真正实现业务逻辑的类,是代理角色所代表的真实对象,是最终要引用的对象;
3、代理角色:对应代理类(Proxy),用来代理和封装真实角色。代理角色内部含有对真实对象的引用,从而可以操作真实对象。同时,代理对象可以在执行真是对象操作时,添加或去除其他操作,相当于对真实对象进行封装;
4、客户角色:对应客户端,使用代理类和主题接口完成一些工作。

3、代理优点

  • 隐藏委托类的实现,调用者只需要和代理类进行交互即可
  • 解耦,在不改变委托类代码情况下做一些额外处理,比如添加初始判断及其他公共操作
  • 提供对目标对象额外的服务。如日志处理、权限管理、网络请求

4、代理实现方式

4.1 静态代理

所谓静态代理也就是在程序运行前就已经存在代理类的.class文件,代理类和委托类的关系在运行前就确定了。

示例
接口

public interface Moveable {
	void move();
}

被代理对象Car(不可改)

public class Car implements Moveable {

	@Override
	public void move() {
		//实现开车
		try {
			Thread.sleep(new Random().nextInt(1000));
			System.out.println("汽车行驶中....");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

}

代理类

public class CarLogProxy implements Moveable {

	public CarLogProxy(Moveable m) {
		super();
		this.m = m;
	}

	private Moveable m;
	
	@Override
	public void move() {
		System.out.println("日志开始....");
		m.move();
		System.out.println("日志结束....");
	}
}

测试类

	public static void main(String[] args) {
		//获取被代理对象
		Car car = new Car();
		//往代理类中传入被代理对象
		CarLogProxy clp = new CarLogProxy(car);
		ctp.move();//先记录日志后记录时间
	}
4.2 动态代理

定义
通过动态代码可实现对不同类、不同方法的代理。动态代理的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以不存在代理类的字节码文件(.class)。代理类和委托类的关系在程序运行时确定

原理
Java动态代理类位于java.lang.reflect包下,一般主要涉及到以下两个类:
(1)Interface InvocationHandler
InvocationHandler是负责连接代理类和委托类的中间类必须实现的接口,它自定义了一个 invoke 方法,用于集中处理在动态代理类对象上的方法调用,每次生成动态代理对象都邀制定一个对应的调用处理器对象,通常在该方法中实现对委托类的代理访问。

/**
*  obj指代理类的实例
*  method指被代理的方法
*  args是该方法的参数数组
**/
public object invoke(Object obj,Method method,Object[] args)

该方法也是InvocationHandler接口所定义的唯一的一个方法,该方法负责集中处理动态代理类上的所有方法的调用。调用处理器根据这三个参数进行预处理或分派到委托类实例上执行。
(2)Proxy class动态代理类
Proxy是 Java 动态代理机制的主类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。

/**
*  loader  类加载器
*  interfaces 实现接口
*  h  InvocationHandler 
**/
static Object newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h)

返回代理类的一个实例,返回后的代理类可以当做被代理类使用(可使用被代理类在接口中声明过的方法)
该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例

动态代理实现示例

步骤1:创建一个实现接口InvocationHandler的调用处理器,它必须实现invoke方法

public class TimeHandler implements InvocationHandler {
//动态代理类对应的调用处理程序类(时间处理器)
	public TimeHandler(Object target) {
		super();
		this.target = target;
	}
//代理类持有一个委托类的对象引用
	private Object target;
	
	/*
	 * 参数:
	 * proxy  被代理对象
	 * method  被代理对象的方法
	 * args 方法的参数
	 * 
	 * 返回值:
	 * Object  方法的返回值
	 * */
	@Override
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		long starttime = System.currentTimeMillis();
		System.out.println("汽车开始行驶....");
		method.invoke(target);//调用被代理对象的方法(Car的move方法)
		long endtime = System.currentTimeMillis();
		System.out.println("汽车结束行驶....  汽车行驶时间:" 
				+ (endtime - starttime) + "毫秒!");
		return null;
	}
}

TimeHandler实现了InvocationHandler的invoke方法,当代理对象的方法被调用时,invoke方法会被回调。其中proxy表示实现了公共代理方法的动态代理对象

步骤2:创建被代理的类以及接口
接口

public interface Moveable {
	void move();
}

被代理类

public class Car implements Moveable {

	@Override
	public void move() {
		//实现开车
		try {
			Thread.sleep(new Random().nextInt(1000));
			System.out.println("汽车行驶中....");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

步骤3:调用Proxy的静态方法newProxyInstance,提供ClassLoader和代理接口类型数组动态创建一个代理类,并通过代理调用方法

//客户端,使用代理类和主题接口完成功能
public class Test {
	/**
	 * JDK动态代理测试类
	 */
	public static void main(String[] args) {
		Car car = new Car();
		InvocationHandler h = new TimeHandler(car);
		Class<?> cls = car.getClass();
		/**
		 * loader  类加载器
		 * interfaces  实现接口
		 * h InvocationHandler
		 */
		Moveable m = (Moveable)Proxy.newProxyInstance(cls.getClassLoader(),
												cls.getInterfaces(), h);//获得动态代理对象,动态代理对象与代理对象实现同一接口
		m.move();//调用动态代理的move方法
	}
}

上面代码通过InvocationHandler handler=new TimeHandler(target);将委托对象作为构造方法的参数传递给了TimeHandler来作为代理方法调用的对象。当我们调用代理对象的move()方法时,该调用将会被转发到TimeHandler对象的invoke上,从而达到动态代理的效果
JDK动态代理:只能代理实现了接口的类,没有实现接口的类不能实现JDK的动态代理
CGLIB动态代理:针对类来实现代理,对指定目标类产生一个子类,通过方法拦截技术拦截所有父类方法的调用。

静态代理与动态代理区别

1、静态代理
优点:
业务类只需要关注业务逻辑本身,保证了业务类的重用性。这是代理的共有优点。
缺点:
(1)代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了。
(2)如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
(3)采用静态代理模式,那么真实角色(委托类)必须事先已经存在的,并将其作为代理对象代理对象内部属性。但是实际使用时,一个真实角色必须对应一个代理角色,如果大量使用会导致类的急剧膨胀。
2、动态代理
优点
1、动态代理类的字节码在程序运行时由Java反射机制动态生成,无需程序员手工编写它的源代码。
2、动态代理类不仅简化了编程工作,而且提高了软件系统的可扩展性,因为Java 反射机制可以生成任意类型的动态代理类。
缺点
JDK的动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能实现JDK的动态代理,cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。

3.2.3 装饰模式
定义

动态的给一个对象添加一些额外的职责。就增加功能来说,装饰模式比生成子类(继承)更为灵活的方案

装饰者模式和代理模式区别
  • 装饰模式是对客户端以透明的方式扩展对象的功能,是继承关系的一种替代;而代理模式则是给一个对象提供一个代理对象,并由代理对象来控制对原有对象的引用
  • 装饰模式应该为所装饰的对象增加功能,而代理对象对所代理的对象施加控制,但不对对象本身的功能进行增强
3.2.4 外观模式
定义

外观模式(Facade Pattern)隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口。这种类型的设计模式属于结构型模式,它向现有的系统添加一个接口,来隐藏系统的复杂性。
这种模式涉及到一个单一的类,该类提供了客户端请求的简化方法和对现有系统类方法的委托调用。
用于为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

特点

1、外观模式是一个高频率使用的设计模式,关键就在于封装
2、外观模式对外隐藏了子系统的实现细节,减少了客户端对子系统的耦合,能拥抱变化
3、外观类对子系统的接口封装,使得系统更易于使用

示例

背景:我们的手机可以看作是一个外观类,而手机中的功能,比如打电话、相机则是各种子模块。
打电话的功能:

//电话功能接口
public interface Phone {
    //打电话
    public void dail();

    //挂断
    public void hangup();
}

//电话的实现类
public class PhoneImpl implements Phone {

    @Override
    public void dail() {
        System.out.println("打电话");
    }

    @Override
    public void hangup() {
        System.out.println("挂断");
    }
}

手机:

public class MobilePhone {
    private Phone mPhone = new PhoneImpl();
    private Camera mCamera = new MiCamera();
    
    //拍照
    public void takePhoto() {
        mCamera.open();
        mCamera.takePhoto();
        mCamera.close();
    }

    //视频聊天
    public void videoChat() {
        mCamera.open();
        mPhone.dail();
    }
}

我们使用手机时,需要用到拍照和视频聊天的功能,但我们不需要知道相机的信息,也不需要知道视频聊天要用到了哪些类,Phone的接口和实现等,只需要用到手机MobilePhone这个类和它提供的接口takePhoto和videoChat

MobilePhone mobilePhone = new MobilePhone();
mobilePhone.takePhoto();
mobilePhone.videoChat();
Android应用场景

很多的第三方SDK,比如友盟统计
我们平时开发过程中封装的模块,比如网络模块、ImageLoader模块等

3、行为型

3.3.1 观察者模式
定义

定义对象间的一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。
适用于事件多级触发场景、跨系统消息交换场景,如消息队列、事件总线的处理机制。

角色

在这里插入图片描述

步骤示例

步骤1:创建Subject类(被观察者)

public class Subject {
   
   private List<Observer> observers 
      = new ArrayList<Observer>();
   private int state;
 
   public int getState() {
      return state;
   }
 
   public void setState(int state) {
   //状态改变时,通知所有观察者
      this.state = state;
      notifyAllObservers();
   }
 
   public void attach(Observer observer){
      observers.add(observer);      
   }
 
   public void notifyAllObservers(){
   //遍历观察者列表,通知每一个观察者
      for (Observer observer : observers) {
         observer.update();
      }
   }  
}

步骤2:创建Observer类(抽象观察者)

public abstract class Observer {
   protected Subject subject;
   public abstract void update();
}

步骤3:创建实体观察者

public class BinaryObserver extends Observer{
 
   public BinaryObserver(Subject subject){
      this.subject = subject;
      this.subject.attach(this);
   }
 
   @Override
   public void update() {
      System.out.println( "Binary String: " 
      + Integer.toBinaryString( subject.getState() ) ); 
   }
}

public class OctalObserver extends Observer{
 
   public OctalObserver(Subject subject){
      this.subject = subject;
      this.subject.attach(this);
   }
 
   @Override
   public void update() {
     System.out.println( "Octal String: " 
     + Integer.toOctalString( subject.getState() ) ); 
   }
}


public class HexaObserver extends Observer{
 
   public HexaObserver(Subject subject){
      this.subject = subject;
      this.subject.attach(this);
   }
 
   @Override
   public void update() {
      System.out.println( "Hex String: " 
      + Integer.toHexString( subject.getState() ).toUpperCase() ); 
   }
}

步骤4:使用

public class ObserverPatternDemo {
   public static void main(String[] args) {
      Subject subject = new Subject();
 
      new HexaObserver(subject);
      new OctalObserver(subject);
      new BinaryObserver(subject);
 
      System.out.println("First state change: 15");   
      subject.setState(15);
      System.out.println("Second state change: 10");  
      subject.setState(10);
   }
}
应用实例解析(Observable,Observer)

JDK中有Observable类和Observer接口,观察者实现Observer接口,被观察者继承Observable类,被观察者通过Observable类的addObserver方法添加观察者。
观察者

public class MyObserver implements Observer{
    private String mName;

    public MyObserver(String name) {
        mName = name;
    }

    @Override
    public void update(Observable o, Object arg) {
        System.out.println(mName + "-->" + "update: " + arg);
    }
}
Observer接口就一个update方法,Observable表示被观察者,Object表示被观察者更新的东西

被观察者

public class MyObservable extends Observable{
    public void sendChangeMeg(String content) {
        //方法继承自Observable,标示状态或是内容发生改变
        setChanged();

        //方法继承自Observable,通知所有观察者,最后会调用每个Observer的update方法
        notifyObservers(content);
    }
}

被观察者通过setChanged()方法标示改变,通过notifyObservers方法通知所有观察者
notifyObservers方法会遍历所有的观察者Observer,并调用它们的update方法,
notifyObservers方法中的参数就是最后传到观察者update方法的参数Object arg

使用

public class ObserverPatternTest {
    @Test
    public void test1() throws Exception {
        MyObservable myObservable = new MyObservable();

        MyObserver myObserver1 = new MyObserver("observer-1");
        MyObserver myObserver2 = new MyObserver("observer-2");
        myObservable.addObserver(myObserver1);
        myObservable.addObserver(myObserver2);

        //发布消息
        myObservable.sendChangeMeg("发布更新啦");
    }
}
原理解析

Observable源码
首先在生成Observable对象时,会初始化一个ArrayList,用于保存所有的观察者Observer
当我们调用notifyObservers时,会循环遍历调用所有添加的观察者Observer,并调用Observer的update方法,而遍历的顺序是从最后添加的一个Observer开始的,所以会有我们上面测试结果图片的情况(从后往前调用)
Observer:
解耦的关键就在于,Observer是一个接口,而我们的观察者都实现了这个接口

Android中应用场景

常见的发布-订阅模式
ListView的Adapter的notifyDataSetChanged更新方法
BroadcastReceiver与LocalBroadcastReceiver
开源库EventBus
RxJava

3.3.2 责任链模式
定义

在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。

作用

避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。
职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了。

示例

背景:日志打印。
我们创建抽象类 AbstractLogger,带有详细的日志记录级别。然后我们创建三种类型的记录器,都扩展了 AbstractLogger。每个记录器消息的级别是否属于自己的级别,如果是则相应地打印出来,否则将不打印并把消息传给下一个记录器

步骤 1:创建抽象的记录器类。

AbstractLogger.java
public abstract class AbstractLogger {
   public static int INFO = 1;
   public static int DEBUG = 2;
   public static int ERROR = 3;
 
   protected int level;
 
   //责任链中的下一个元素
   protected AbstractLogger nextLogger;
 
   public void setNextLogger(AbstractLogger nextLogger){
      this.nextLogger = nextLogger;
   }
 
   public void logMessage(int level, String message){
      if(this.level <= level){
         write(message);
      }
      if(nextLogger !=null){
         nextLogger.logMessage(level, message);
      }
   }
 
   abstract protected void write(String message);
   
}

步骤 2:创建扩展了该记录器类的实体类。

ConsoleLogger.java
public class ConsoleLogger extends AbstractLogger {
 
   public ConsoleLogger(int level){
      this.level = level;
   }
 
   @Override
   protected void write(String message) {    
      System.out.println("Standard Console::Logger: " + message);
   }
}
ErrorLogger.java
public class ErrorLogger extends AbstractLogger {
 
   public ErrorLogger(int level){
      this.level = level;
   }
 
   @Override
   protected void write(String message) {    
      System.out.println("Error Console::Logger: " + message);
   }
}
FileLogger.java
public class FileLogger extends AbstractLogger {
 
   public FileLogger(int level){
      this.level = level;
   }
 
   @Override
   protected void write(String message) {    
      System.out.println("File::Logger: " + message);
   }
}

步骤 3:创建不同类型的记录器。赋予它们不同的错误级别,并在每个记录器中设置下一个记录器。每个记录器中的下一个记录器代表的是链的一部分。

ChainPatternDemo.java
public class ChainPatternDemo {
   
   private static AbstractLogger getChainOfLoggers(){
 
      AbstractLogger errorLogger = new ErrorLogger(AbstractLogger.ERROR);
      AbstractLogger fileLogger = new FileLogger(AbstractLogger.DEBUG);
      AbstractLogger consoleLogger = new ConsoleLogger(AbstractLogger.INFO);
 
      errorLogger.setNextLogger(fileLogger);
      fileLogger.setNextLogger(consoleLogger);
 
      return errorLogger;  
   }
 
   public static void main(String[] args) {
      AbstractLogger loggerChain = getChainOfLoggers();
 
      loggerChain.logMessage(AbstractLogger.INFO, "This is an information.");
 
      loggerChain.logMessage(AbstractLogger.DEBUG, 
         "This is a debug level information.");
 
      loggerChain.logMessage(AbstractLogger.ERROR, 
         "This is an error information.");
   }
}

步骤 4:执行程序,输出结果

Standard Console::Logger: This is an information.
File::Logger: This is a debug level information.
Standard Console::Logger: This is a debug level information.
Error Console::Logger: This is an error information.
File::Logger: This is an error information.
Standard Console::Logger: This is an error information.
Android中应用场景

Okhttp中的拦截器

四、总结面试题

1、了解哪些设计模式

2、建造者模式与工厂模式的区别

3、代理模式分类、区别与原理(依托Retrofit实际使用为例)

4、责任链模式(Okhttp源码流程与用到的设计模式详解)

5、观察者模式(EventBus、LocalBrocastReceive、Rxjava)原理详解

java设计模式大体上分为三大类: 创建型模式(5种):工厂方法模式,抽象工厂模式,单例模式,建造者模式,原型模式。 结构型模式(7种):适配器模式,装饰器模式,代理模式,外观模式,桥接模式,组合模式,享元模式。 行为型模式(11种):策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。 设计模式遵循的原则有6个: 1、开闭原则(Open Close Principle)   对扩展开放,对修改关闭。 2、里氏代换原则(Liskov Substitution Principle)   只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。 3、依赖倒转原则(Dependence Inversion Principle)   这个是开闭原则的基础,对接口编程,依赖于抽象而不依赖于具体。 4、接口隔离原则(Interface Segregation Principle)   使用多个隔离的借口来降低耦合度。 5、迪米特法则(最少知道原则)(Demeter Principle)   一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。 6、合成复用原则(Composite Reuse Principle)   原则是尽量使用合成/聚合的方式,而不是使用继承。继承实际上破坏了类的封装性,超类的方法可能会被子类修改。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值