设计模式篇 - 开源项目中的结构型设计模式

昨天写了创建型设计模式,今天的主题是结构型设计模式。结构型模式共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。和上一篇一样,还是围绕着开源项目中的设计模式来展开理解。

 

目录:

  1. 适配器模式简介和应用
  2. 装饰器模式简介和应用
  3. 代理模式简介和应用
  4. 外观模式简介和应用
  5. 桥接模式简介和应用
  6. 组合模式简介和应用
  7. 享元模式简介和应用

 

 

1. 适配器模式简介和应用

 

  • 1.1 简介

适配器模式的别名为包装器 (Wrapper) 模式,适配器模式将某个类的接口转换成客户端期望的另一个接口表示,目的是消除由于接口不匹配所造成的类的兼容性问题。主要分为三类:类的适配器模式、对象的适配器模式、接口的适配器模式。

适用场景

  • 1. 系统需要使用现有的类,但现有的类却不兼容。 
  • 2. 需要建立一个可以重复使用的类,用于一些彼此关系不大的类,并易于扩展,以便于面对将来会出现的类。 
  • 3. 需要一个统一的输出接口,但是输入类型却不可预知。

举个例子:手机充电需要将220V 的交流电转化为手机锂电池需要的5V 直流电,我们的目标就是写一个电源适配器,将 AC220v 转换成 DC5V,我们用三种适配器模式来实现这个功能。

 

  • 1.2 类的适配器模式
    /**
     * Source, 源电伏
     */
    private static class AC220 {

        public int output220V() {
            return 220;
        }
    }

    /**
     * Destination, 目标电伏
     */
    private interface DC5 {

        int output5V();
    }

    /**
     * Adapter, 适配器
     */
    private static final class PowerAdapter extends AC220 implements DC5 {

        @Override
        public int output5V() {
            return output220V() / 44;
        }
    }

    public static void main(String[] args) {
        DC5 dc5 = new PowerAdapter();
        dc5.output5V();
    }

因为 Java 单继承的缘故,Destination 必须是接口,以便于 Adapter 去继承 Source 并实现 Destination,完成适配的功能,但这样就导致了Adapter 里暴露了Source 类的方法,使用起来的成本就增加了。

 

  • 1.3 对象适配器模式

现在用对象适配器模式来实现上面的功能,保留 AC220 和 DC5 两个基本类,我们让 Adapter 持有 Destination 类的实例,然后再实现 DC5,以这种持有对象的方式来实现适配器功能:

    /**
     * Source, 源电伏
     */
    private static class AC220 {

        public int output220V() {
            return 220;
        }
    }

    /**
     * Destination, 目标电伏
     */
    private interface DC5 {

        int output5V();
    }

    /**
     * Adapter, 适配器
     */
    private static final class PowerAdapter implements DC5 {

        private final AC220 ac220;

        PowerAdapter(AC220 ac220) {
            this.ac220 = ac220;
        }

        @Override
        public int output5V() {
            return ac220.output220V() / 44;
        }
    }

    public static void main(String[] args) {
        DC5 dc5 = new PowerAdapter(new AC220());
        dc5.output5V();
    }

对象适配器和类适配器其实算是同一种思想,只不过实现方式不同,这种和后面的装饰器模式写法类似,但是最终作用不一样。

 

  • 1.4 接口的适配器模式

对于接口适配器模式,就不用只限制于220->5,我们的接口可以有更多的抽象方法,这一点在 Android 开发中有很多影子,动画的适配器有很多接口,但我们只需要关心我们需要的回调方法(详见 AnimatorListenerAdapter 类),我们把接口比作万能适配器:

    private static class AC220 {

        public int output220V() {
            return 220;
        }
    }

    private interface DCOutput {

        int output5V();

        int output9V();

        int output12V();

        int output24V();
    }

    private abstract class PowerAdapter implements DCOutput {

        protected AC220 mAC220;

        public PowerAdapter(AC220 ac220) {
            this.mAC220 = ac220;
        }

        @Override
        public int output5V() {
            return 0;
        }

        @Override
        public int output9V() {
            return 0;
        }

        @Override
        public int output12V() {
            return 0;
        }

        @Override
        public int output24V() {
            return 0;
        }
    }

    private final class Power5VAdapter extends PowerAdapter {

        public Power5VAdapter(AC220 ac220) {
            super(ac220);
        }

        @Override
        public int output5V() {
            return mAC220.output220V() / 44;
        }
    }

    public static void main(String[] args) {
        Power5VAdapter power5VAdapter = new TestStructDesignModel().new Power5VAdapter(new AC220());
        power5VAdapter.output5V();
    }

 

  • 1.5 Android animator 中的适配器模式

上面讲过 Android 中的动画回调使用了接口适配器模式,来看看它的实现:

    // android.animation.Animator
    public static interface AnimatorListener {
        
        void onAnimationStart(Animator animation);

        void onAnimationEnd(Animator animation);

        void onAnimationCancel(Animator animation);

        void onAnimationRepeat(Animator animation);
    }

AnimatorListener 可以看作是 Destination。

public abstract class AnimatorListenerAdapter implements Animator.AnimatorListener,
        Animator.AnimatorPauseListener {

    @Override
    public void onAnimationCancel(Animator animation) {
    }

    @Override
    public void onAnimationEnd(Animator animation) {
    }

    @Override
    public void onAnimationRepeat(Animator animation) {
    }

    @Override
    public void onAnimationStart(Animator animation) {
    }

    @Override
    public void onAnimationPause(Animator animation) {
    }

    @Override
    public void onAnimationResume(Animator animation) {
    }
}

AnimatorListenerAdapter 是适配器,使用时,只需要实现 AnimatorListenerAdapter 中你需要的方法即可。这边缺少一个 Source,算是一种变形的接口适配器模式,这种在开发中使用的非常多。

 

 

2. 装饰器模式简介和应用

 

  • 2.1 简介

装饰器模式允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。

意图:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。

主要解决:一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。

何时使用:在不想增加很多子类的情况下扩展类。

优点:装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。

缺点:多层装饰比较复杂。

 

  • 2.2 JDK IO 的装饰器模式

JDK IO 这块使用了装饰器模式,我们一起来看看。

首先是被装饰类 InputStream,目前它的功能只有以下这些:

public abstract class InputStream implements Closeable {
private static final int MAX_SKIP_BUFFER_SIZE = 2048;
    public abstract int read() throws IOException;
    public int read(byte b[]) throws IOException {}
    public int read(byte b[], int off, int len) throws IOException {}
    public long skip(long n) throws IOException {}
    public int available() throws IOException {}
    public void close() throws IOException {}
    public synchronized void mark(int readlimit) {}
    public synchronized void reset() throws IOException {}
    public boolean markSupported() {}
}

下面是作为装饰模式的抽象装饰角色 FilterInputStream 类的源代码。可以看出,FilterInputStream 的接口与 InputStream 的接口是完全一致的。也就是说,直到这一步,还是与装饰模式相符合的。

public class FilterInputStream extends InputStream {
    protected volatile InputStream in;
    protected FilterInputStream(InputStream in) {
        this.in = in;
    }
    public int read() throws IOException {
        return in.read();
    }
    public int read(byte b[]) throws IOException {
        return read(b, 0, b.length);
    }
    public int read(byte b[], int off, int len) throws IOException {
        return in.read(b, off, len);
    }
    public long skip(long n) throws IOException {
        return in.skip(n);
    }
    public int available() throws IOException {
        return in.available();
    }
    public void close() throws IOException {
        in.close();
    }
    public synchronized void mark(int readlimit) {
        in.mark(readlimit);
    }
    public synchronized void reset() throws IOException {
        in.reset();
    }
    public boolean markSupported() {
        return in.markSupported();
    }
}

下面是具体装饰角色 PushbackInputStream 的源代码。

public class PushbackInputStream extends FilterInputStream {
    private void ensureOpen() throws IOException {}
    public PushbackInputStream(InputStream in, int size) {}
    public PushbackInputStream(InputStream in) {}
    public int read() throws IOException {}
    public int read(byte[] b, int off, int len) throws IOException {}
    public void unread(int b) throws IOException {}
    public void unread(byte[] b, int off, int len) throws IOException {}
    public void unread(byte[] b) throws IOException {}
    public int available() throws IOException {}
    public long skip(long n) throws IOException {}
    public boolean markSupported() {}
    public synchronized void mark(int readlimit) {}
    public synchronized void reset() throws IOException {}
    public synchronized void close() throws IOException {}
}

查看源码可以发现,这个装饰类提供了额外的方法 unread(),这就意味着 PushbackInputStream 是一个半透明的装饰类。换言 之,它破坏了理想的装饰模式的要求。如果客户端持有一个类型为 InputStream 对象的引用 in 的话,那么如果 in 的真实类型是 PushbackInputStream 的话,只要客户端不需要使用 unread() 方法,那么客户端一般没有问题。但是如果客户端必须使用这个方法,就必须进行向下类型转换。将 in 的类型转换成为 PushbackInputStream 之后才可能调用这个方法。但是这个类型转换意味着客户端必须知道它拿到的引用是指向一个类型为 PushbackInputStream 的对象,这就破坏了使用装饰模式的原始用意。

现实世界与理论总归是有一段差距的,纯粹的装饰模式在真实的系统中很难找到,一般所遇到的,都是这种半透明的装饰模式。

 

 

 

3. 代理模式简介和应用

 

  • 3.1 简介

代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗的来讲代理模式就是我们生活中常见的中介。

关于代理模式,详细的可以查看我之前的文章:Java篇 - 代理模式和动态代理实现原理

 

  • 3.2 Android Framework 中的代理模式

熟悉 Android Framework 的同学都知道,Android 的 binder 机制是 C/S 架构,比如 ActivityManagerProxy 对应 ActivityManagerService,下面的 startActivity 的启动流程图:

主要代码在 ActivityManagerNative 中:

/** {@hide} */
// 继承Binder,作为服务端
public abstract class ActivityManagerNative extends Binder implements IActivityManager
{
    static public IActivityManager asInterface(IBinder obj) {
        if (obj == null) {
            return null;
        }
        IActivityManager in =
            (IActivityManager)obj.queryLocalInterface(descriptor);
        if (in != null) {
            return in;
        }

        return new ActivityManagerProxy(obj);
    }

    // getDefault返回IActivityManager
    static public IActivityManager getDefault() {
        return gDefault.get();
    }

    public IBinder asBinder() {
        return this;
    }

    /**
     * 从ServiceManager中先获取服务,IActivityManager服务。从ServiceManager中请求到的服务,以ActivityManagerProxy返回,
     * 是一个代理,然后与ActivityManagerNative(服务端),然后ActivityManagerNative与ActivityManagerService直接沟通.
     */
    // System Server从zygote fork而来,是管理整个java framework层的service
    private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
        protected IActivityManager create() {
            // 先从ServiceManager中获取ActivityManagerService
            IBinder b = ServiceManager.getService("activity");
            if (false) {
                Log.v("ActivityManager", "default service binder = " + b);
            }
            // proxy
            IActivityManager am = asInterface(b);
            if (false) {
                Log.v("ActivityManager", "default service = " + am);
            }
            return am;
        }
    };
}

// 先访问 client端的 ActivityManagerProxy
class ActivityManagerProxy implements IActivityManager
{
    public ActivityManagerProxy(IBinder remote)
    {
        mRemote = remote;
    }

    public IBinder asBinder()
    {
        return mRemote;
    }

    public int startActivity(IApplicationThread caller, String callingPackage, Intent intent,
            String resolvedType, IBinder resultTo, String resultWho, int requestCode,
            int startFlags, ProfilerInfo profilerInfo, Bundle options) throws RemoteException {
    
    }

    private IBinder mRemote;
}

这块的逻辑非常复杂,我这边精简了部分代码,主要逻辑是应用层发起 startActivity 请求,先会找到代理 ActivityManagerProxy,然后让它通过 IPC 请求 ActivityManagerNative。这边是一种静态代理的实现方式,ActivityManagerProxy 和 ActivityManagerNative 都实现了 IActivityManager 接口。

 

 

 

4. 外观模式简介和应用

 

  • 4.1 简介

外观模式的目的不是给予子系统添加新的功能接口,而是为了让外部减少与子系统内多个模块的交互,松散耦合,从而让外部能够更简单地使用子系统。外观模式的本质是:封装交互,简化调用。

 

这个模式非常简单,而且使用的很多,就是简化外部的调用,把多次调用封装到一起。

 

  • 4.2 ShareSDK 的外观模式

ShareSDK 可以很方便的集成第三方平台的分享功能,特别是一键分享,客户端连 UI 都可以省了。它就是简化了外部的调用,只需几句代码就可以实现分享功能。

比如这边接入方封装了一个分享工具类,ShareUtils:

public final class ShareUtils {

    public ShareUtils() {
        throw new RuntimeException("ShareUtils stub!");
    }

    public static void toShare(
        Activity activity, @NonNull SharePlatform platform, @NonNull ShareMode shareMode, @NonNull ShareContent shareContent,
        PlatformActionListener listener) {
        if (!isSecureContextForUI(activity))
            return;
        OnekeyShare oks = new OnekeyShare();
        oks.setPlatform(platform.getPlatform());
        // 设置参数 ...
        oks.show(activity);
    }
}

调用 OnekeyShare 对象的 show 方法即可实现分享功能:

    public void show(Context context) {
        HashMap<String, Object> shareParamsMap = new HashMap();
        shareParamsMap.putAll(this.params);
        if (!(context instanceof MobApplication)) {
            MobSDK.init(context.getApplicationContext());
        }

        ShareSDK.logDemoEvent(1, (Platform)null);
        int iTheme = 0;

        try {
            iTheme = ResHelper.parseInt(String.valueOf(shareParamsMap.remove("theme")));
        } catch (Throwable var6) {
            ;
        }

        OnekeyShareTheme theme = OnekeyShareTheme.fromValue(iTheme);
        OnekeyShareThemeImpl themeImpl = theme.getImpl();
        themeImpl.setShareParamsMap(shareParamsMap);
        themeImpl.setDialogMode(shareParamsMap.containsKey("dialogMode") ? (Boolean)shareParamsMap.remove("dialogMode") : false);
        themeImpl.setSilent(shareParamsMap.containsKey("silent") ? (Boolean)shareParamsMap.remove("silent") : false);
        themeImpl.setCustomerLogos((ArrayList)shareParamsMap.remove("customers"));
        themeImpl.setHiddenPlatforms((HashMap)shareParamsMap.remove("hiddenPlatforms"));
        themeImpl.setPlatformActionListener((PlatformActionListener)shareParamsMap.remove("callback"));
        themeImpl.setShareContentCustomizeCallback((ShareContentCustomizeCallback)shareParamsMap.remove("customizeCallback"));
        if (shareParamsMap.containsKey("disableSSO") && (Boolean)shareParamsMap.remove("disableSSO")) {
            themeImpl.disableSSO();
        }

        themeImpl.show(context.getApplicationContext());
    }

可以看到,show 方法的实现里面做了很多配置和初始化工作,最后只提供一个简单的入口供接入方调用。

 

 

 

5. 桥接模式简介和应用

 

  • 5.1 简介

桥接模式即将抽象部分与它的实现部分分离开来,使它们都可以独立变化。桥接模式将继承关系转化成关联关系,它降低了类与类之间的耦合度,减少了系统中类的数量,也减少了代码量。

优点:

  • 分离抽象接口及其实现部分,提高了比继承更好的解决方案。
  • 桥接模式提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统。
  • 实现细节对客户透明,可以对用户隐藏实现细节。

缺点:

  • 桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。 
  • 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性。

 

  • 5.2 一个小例子帮助理解

    public abstract class Shape {

        Color color;

        void setColor(Color color) {
            this.color = color;
        }

        public abstract void draw();
    }

    public class Circle extends Shape {

        public void draw() {
            color.bepaint("Circle");
        }
    }

    public class Rectangle extends Shape {

        public void draw() {
            color.bepaint("Rectangle");
        }
    }

    public class Square extends Shape {

        public void draw() {
            color.bepaint("Square");
        }
    }

    public interface Color {

        void bepaint(String shape);
    }

    public class White implements Color {

        public void bepaint(String shape) {
            System.out.println("White: " + shape);
        }
    }

    public class Gray implements Color {

        public void bepaint(String shape) {
            System.out.println("Gray: " + shape);
        }
    }

    public class Black implements Color {

        public void bepaint(String shape) {
            System.out.println("Black: " + shape);
        }
    }

    public static void main(String[] args) {
        Color white = new TestStructDesignModel().new White();
        Shape square = new TestStructDesignModel().new Square();
        square.setColor(white);
        square.draw();

        Shape rectange = new TestStructDesignModel().new Rectangle();
        rectange.setColor(white);
        rectange.draw();
    }

总结:

  • 桥接模式实现了抽象化与实现化的脱耦。它们两个互相独立,不会影响到对方。
  • 对于两个独立变化的维度,使用桥接模式再适合不过了。
  • 对于"具体的抽象类"所做的改变,是不会影响到客户。

 

  • 5.3 Android AdapterView 的桥接模式

桥接模式在 Android 中运用的非常广泛,比如 Adapter 跟 AdapterView 之间就是一个桥接模式。

Adapter 接口:

public interface Adapter {
   
    void registerDataSetObserver(DataSetObserver observer);

    void unregisterDataSetObserver(DataSetObserver observer);

    int getCount();   
    
    Object getItem(int position);
   
    long getItemId(int position);
    
    boolean hasStableIds();
  
    View getView(int position, View convertView, ViewGroup parent);

    static final int IGNORE_ITEM_VIEW_TYPE = AdapterView.ITEM_VIEW_TYPE_IGNORE;
  
    int getItemViewType(int position);
   
    int getViewTypeCount();
    
    static final int NO_SELECTION = Integer.MIN_VALUE;
 
    boolean isEmpty();
}

ListAdapter 接口:

public interface ListAdapter extends Adapter {

    public boolean areAllItemsEnabled();

    boolean isEnabled(int position);
}

AdapterView:

public abstract class AdapterView<T extends Adapter> extends ViewGroup {
    Adapter public abstract T getAdapter();
    public abstract void setAdapter(T adapter);
    // ......
 }

接着来看 ListView 的父类 AbsListView,继承自 AdapterView:

public abstract class AbsListView extends AdapterView<ListAdapter>
     // 继承自 AdapterView, 并且指明了 T 为 ListAdapter
     ListAdapter mAdapter;
     // ......
     // 这里实现了setAdapter 的方法,实例了对实现化对象的引用
     public void setAdapter(ListAdapter adapter) {
     // 这的 adapter 是从子类传入上来,也就是 listview,拿到了具体实现化的对象
     if (adapter != null) {
         mAdapterHasStableIds = mAdapter.hasStableIds();
         if (mChoiceMode != CHOICE_MODE_NONE && mAdapterHasStableIds &&   mCheckedIdStates == null) {
             mCheckedIdStates = new LongSparseArray<Integer>();
         }
     }
     if (mCheckStates != null) {
         mCheckStates.clear();
    }
     if (mCheckedIdStates != null) {
         mCheckedIdStates.clear();
    }
 }

大家都知道,构建一个 listview,adapter 中最重要的两个方法,getCount() 告知数量,getview() 告知具体的 view 类型,接下来看看 AbsListView 作为一个视图的集合是如何来根据实现化对象 adapter 来实现的具体的 view 呢?

 protected void onAttachedToWindow() {
    super.onAttachedToWindow();
     // ......
     // 这里在加入 window 的时候,getCount() 确定了集合的个数
     mDataChanged = true;
     mOldItemCount = mItemCount;
     mItemCount = mAdapter.getCount();
 }
View obtainView(int position, boolean[] isScrap) {
     // ......
     ​// 这里根据位置显示具体的 view, return 的 child 是从持有的实现对象 mAdapter 里面的具体实现的
     ​// 方法 getview 来得到的。
     final View child = mAdapter.getView(position, scrapView, this);
     // ......
     return child;
 }

下面的 UML 图即为 Android 源码中的桥接模式实现:

将不同的接口桥接起来一起实现功能,接口之间互相隔离。

 

 

 

6. 组合模式简介和应用

 

  • 6.1 简介

在现实生活中,存在很多"部分-整体"的关系,例如,大学中的部门与学院、总公司中的部门与分公司、学习用品中的书与书包等。在软件开发中也是这样,例如,文件系统中的文件与文件夹、窗体程序中的简单控件与容器控件等。对这些简单对象与复合对象的处理,如果用组合模式来实现会很方便。组合模式是一种将对象组合成树状的层次结构的模式,用来表示"部分-整体"的关系,使用户对单个对象和组合对象具有一致的访问性。

优点:

  • 组合模式使得客户端代码可以一致地处理单个对象和组合对象,无须关心自己处理的是单个对象,还是组合对象,这简化了客户端代码。
  • 更容易在组合体内加入新的对象,客户端不会因为加入了新的对象而更改源代码,满足"开闭原则"。

缺点:
  • 设计较复杂,客户端需要花更多时间理清类之间的层次关系。
  • 容易限制容器中的构件。
  • 不容易用继承的方法来增加构件的新功能。

 

  • 6.2 Android View 和 ViewGroup 的组合模式

在 Android 的视图树中,容器一定是 ViewGroup,只有 ViewGroup 才能包含其他 View 和 ViewGroup,View 在里面处于树的叶子角色。现在来看看它们是如何利用组合模式组织在一起的,首先在 View 类定义了有关具体操作,然后在 ViewGroup 类中继承    View 类,并添加相关的增加、删除和查找孩子 View 节点,代码如下:

public abstract class ViewGroup extends View implements ViewParent, ViewManager {
}

接着看增加孩子节点函数:

    public void addView(View child) {
    }

    public void addView(View child, int index) {
    }

    public void addView(View child, int width, int height) {
    }

    public void addView(View child, LayoutParams params) {
    }

    public void addView(View child, int index, LayoutParams params) {
    }

  在 ViewGroup 中我们找到了添加 addView() 方法,有了增加孩子节点,肯定有相对应删除孩子节点的方法,接着看:

    public void removeView(View view) {
    }

    public void removeViewInLayout(View view) {
    }

    public void removeViewsInLayout(int start, int count) {
    }

    public void removeViewAt(int index) {
    }
  
    // ......

  而具体叶子节点,如 Button,它是继承 TextView 的,TextView 是继承 View 的,代码如下:

public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
}

常用的容器类 (包装和容纳各种 View),如 LinearLayout、FrameLayout 等,代码如下:

public class LinearLayout extends ViewGroup {
}

public class FrameLayout extends ViewGroup {
}

public class RelativeLayout extends ViewGroup {
}

来张图理解下:

 

 

7. 享元模式简介和应用

 

  • 7.1 简介

享元模式有点类似于单例模式,都是只生成一个对象来被共享使用。这里有个问题,那就是对共享对象的修改,为了避免出现这种情况,我们将这些对象的公共部分,或者说是不变化的部分抽取出来形成一个对象,这个对象就可以避免到修改的问题。

享元的目的是为了减少不必要的内存消耗,将多个对同一对象的访问集中起来,不必为每个访问者创建一个单独的对象,以此来降低内存的消耗。

说到享元模式,第一个想到的应该就是池技术了,String 常量池、数据库连接池、缓冲池等等都是享元模式的应用,所以说享元模式是池技术的重要实现方式。比如我们每次创建字符串对象时,都需要创建一个新的字符串对象的话,内存开销会很大,所以如果第一次创建了字符串对象 "abc",下次再创建相同的字符串 "abc" 时,只是把它的引用指向 "abc",这样就实现了 "abc" 字符串在内存中的共享。

何时使用?

  • 系统中有大量对象时。
  • 这些对象消耗大量内存时。
  • 这些对象的状态大部分可以外部化时。

方法:

  • 用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象,用 HashMap/HashTable 存储。

优点:

  • 大大减少了对象的创建,降低了程序内存的占用,提高效率。

缺点:

  • 提高了系统的复杂度。需要分离出内部状态和外部状态,而外部状态具有固化特性,不应该随着内部状态的改变而改变。

使用场景:

  • 系统中存在大量相似对象。
  • 需要缓冲池的场景。

 

  • 7.2 一个例子熟悉享元模式
    private interface IPerson {

        void doWork();
    }

    private static class Coder implements IPerson {

        private final String name;

        public Coder(String name) {
            this.name = name;
        }

        @Override
        public void doWork() {
            System.out.println("coder" + name + " do work");
        }
    }

    private static final Map<String, IPerson> persons = new HashMap<>();

    private static final class PersonFactory {

        private static final Map<String, IPerson> personMap = new HashMap<>();

        public static IPerson get(String name) {
            IPerson person = personMap.get(name);
            if (person == null) {
                person = new Coder(name);
                personMap.put(name, person);
            }
            return person;
        }

        public static int getSize() {
            return personMap.size();
        }
    }

    public static void main(String[] args) {
        String name = "kzw";
        for (int i = 1; i <= 10; i++) {
            IPerson person = PersonFactory.get(name);
            person.doWork();
            System.out.println("对象池中对象数量为:" + PersonFactory.getSize());
        }
    }

执行输出:

对象池中对象数量为:1
coderkzw do work
对象池中对象数量为:1
coderkzw do work
对象池中对象数量为:1
coderkzw do work
对象池中对象数量为:1
coderkzw do work
对象池中对象数量为:1
coderkzw do work
对象池中对象数量为:1
coderkzw do work
对象池中对象数量为:1
coderkzw do work
对象池中对象数量为:1
coderkzw do work
对象池中对象数量为:1
coderkzw do work
对象池中对象数量为:1

是不是简单又熟悉,其实开发中经常用到,只不过你不会想到原来这就是享元模式。

 

  • 7.3 Java String 的享元模式

Java 中将 String 类定义为不可改变类,JVM 中字符串一般保存在字符串常量池中。具体可以参考我之前的文章:Java篇 - 从内到外剖析String类以及使用String时的性能优化,这边就不再赘述了。

 

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值