【設計模式】創(chuàng)建型設計模式匯總

創(chuàng)建型設計模式匯總

1. 單例模式

1.1 單例模式的定義

一個類只允許創(chuàng)建一個對象或實例膛锭。

1.2 單例模式的作用

  1. 有些數據在系統(tǒng)中只應該保存一份览爵,就比較適合設計為單例模式

1.3 單例模式的經典實現

餓漢式

public class Ehan {
    private static Ehan instance = new Ehan();

    private Ehan() {

    }

    public static Ehan getInstance() {
        return instance;
    }
}

優(yōu)點:線程安全
缺點:無法支持延遲加載


懶漢式

public class Lhan {
    private static Lhan lhan;

    private Lhan() {

    }

    public static synchronized Lhan getInstance() {
        if (lhan == null) {
            lhan = new Lhan();
        }

        return lhan;
    }
}

優(yōu)點:支持延遲加載妇汗,并且線程安全
缺點:獲取對象實例的方法加了一把大鎖孩革,導致函數并發(fā)效率很低际歼。


雙重檢測鎖

public class DoubleCheck {
    private static DoubleCheck instance;

    private DoubleCheck() {

    }

    public static DoubleCheck getInstance() {
        if (instance == null) {
            synchronized (DoubleCheck.class) {
                if (instance == null) {
                    instance = new DoubleCheck();
                }
            }
        }

        return instance;
    }
}

優(yōu)點:線程安全并支持延遲加載惶翻。
缺點:在低版本 Java 中,由于創(chuàng)建對象的操作和初始化(也就是執(zhí)行構造函數)不是原子操作鹅心,在指令重排序后吕粗,有可能會出現 instance 被賦值后,還未初始化就被其它線程調用了旭愧。

解決低版本 Java 中由于指令重排序溯泣,未初始化就被調用的問題

在 instance 成員變量加上 volatile 關鍵字,禁止重排序就可以了榕茧。

靜態(tài)內部類

public class StaticInnerClass {
    private StaticInnerClass() {

    }

    private static class Single {
        private static StaticInnerClass instance = new StaticInnerClass();
    }

    public static StaticInnerClass getInstance() {
        return Single.instance;
    }
}

優(yōu)點:線程安全由 JVM 來保證垃沦,同時支持延遲加載
缺點:無

權舉

public enum SingleEnum {
    INSTANCE;

    private AtomicLong id = new AtomicLong();

    public long getId() {
        return id.incrementAndGet();
    }
}

優(yōu)點:線程安全


1.4 單例模式延遲加載優(yōu)點和缺點

優(yōu)點:當真正需要使用對象的時候,才去創(chuàng)建并初始化對象用押,避免提前初始化導致的內存浪費肢簿。

缺點:如果初始化耗時長腔剂,那真正使用的時候再去加載雀费,會影響系統(tǒng)的性能(比如:會導致接口請求的響應時間變長,甚至超時)。

1.5 單例存在的問題

1. 單例會隱藏類之間的依賴關系

通過構造函數请梢、參數傳遞等方式聲明的類之間的依賴關系,我們通過查看函數的定義途事,就能很容易識別出來布卡。但是,單例類不需要顯示創(chuàng)建卧惜、不需要依賴參數傳遞厘灼,在函數中直接調用就可以了。如果代碼比較復雜咽瓷,這種調用關系就會非常隱蔽设凹。在閱讀代碼的時候,我們就需要仔細查看每個函數的代碼實現茅姜,才能知道這個類到底依賴了哪些單例類闪朱。

2. 單例對代碼的擴展性不友好

如果未來某一天,我們需要在代碼中創(chuàng)建兩個實例或多個實例钻洒,那就要對代碼有比較大的改動奋姿。

在系統(tǒng)設計初期,我們覺得系統(tǒng)中只應該有一個數據庫連接池素标,這樣能方便我們控制對數據庫連接資源的消耗称诗。所以,我們把數據庫連接池類設計成了單例類糯钙。但之后我們發(fā)現粪狼,系統(tǒng)中有些 SQL 語句運行得非常慢。這些 SQL 語句在執(zhí)行的時候任岸,長時間占用數據庫連接資源再榄,導致其他 SQL 請求無法響應。為了解決這個問題享潜,我們希望將慢 SQL 與其他 SQL 隔離開來執(zhí)行困鸥。為了實現這樣的目的,我們可以在系統(tǒng)中創(chuàng)建兩個數據庫連接池剑按,慢 SQL 獨享一個數據庫連接池疾就,其他 SQL 獨享另外一個數據庫連接池,這樣就能避免慢 SQL 影響到其他 SQL 的執(zhí)行艺蝴。

3. 單例對代碼的可測試性不友好

單例模式的使用會影響代碼的可測試性猬腰,當單例類依賴比較重的外部資源,比如:DB猜敢,我們在寫測試用例的時候希望通過 mock 的方式替換掉姑荷。而單例這種硬編碼方式盒延,導致無法實現 mock 替換。

1.6 單例模式的替代方案

  1. 靜態(tài)方法實現
  2. 工廠模式
  3. IOC 容器

1.7 如何理解單例模式的唯一性

單例類中對象唯一性的作用范圍是“進程唯一”的鼠冕。

“集群唯一”指的是進程內唯一添寺,進程間也唯一。

1.8 如何實現一個線程內唯一的單例

使用 HashMap 來存儲對象懈费,key 為線程 ID计露,值為實例對象,保證每個線程都對應一個單例對象憎乙。

當然票罐,也可以通過 Java 中自帶的 ThreadLocal 來實現線程唯一。

1.9 如何實現集群下的單例

需要將單例對象序列化到外部共享存儲區(qū)(如:文件)寨闹。進程在使用這個對象時胶坠,需要先從外部共享存儲區(qū)中將它讀取到內存君账,并反序列化為對象后使用繁堡。同時,為了保證任何時候進程間都只有一份對象存在乡数,一個進程在獲取到對象后椭蹄,需要對對象進行加鎖,避免其它進程再將其獲取净赴。

public class IdGenerator {
  private AtomicLong id = new AtomicLong(0);
  private static IdGenerator instance;
  private static SharedObjectStorage storage = FileSharedObjectStorage(/*入參省略绳矩,比如文件地址*/);
  private static DistributedLock lock = new DistributedLock();
  
  private IdGenerator() {}

  public synchronized static IdGenerator getInstance() 
    if (instance == null) {
      lock.lock();
      instance = storage.load(IdGenerator.class);
    }
    return instance;
  }
  
  public synchroinzed void freeInstance() {
    storage.save(this, IdGeneator.class);
    instance = null; //釋放對象
    lock.unlock();
  }
  
  public long getId() { 
    return id.incrementAndGet();
  }
}

// IdGenerator使用舉例
IdGenerator idGeneator = IdGenerator.getInstance();
long id = idGenerator.getId();
IdGenerator.freeInstance();

1.10 如何實現一個多例模式

多例模式表示可以創(chuàng)建有限多個實例。通過一個 Map 來存儲對象類型和對象之間的對應關系玖翅,來控制對象的個數翼馆。

public class BackendServer {
  private long serverNo;
  private String serverAddress;

  private static final int SERVER_COUNT = 3;
  private static final Map<Long, BackendServer> serverInstances = new HashMap<>();

  static {
    serverInstances.put(1L, new BackendServer(1L, "192.134.22.138:8080"));
    serverInstances.put(2L, new BackendServer(2L, "192.134.22.139:8080"));
    serverInstances.put(3L, new BackendServer(3L, "192.134.22.140:8080"));
  }

  private BackendServer(long serverNo, String serverAddress) {
    this.serverNo = serverNo;
    this.serverAddress = serverAddress;
  }

  public BackendServer getInstance(long serverNo) {
    return serverInstances.get(serverNo);
  }

  public BackendServer getRandomInstance() {
    Random r = new Random();
    int no = r.nextInt(SERVER_COUNT)+1;
    return serverInstances.get(no);
  }
}

2. 工廠模式

2.1 工廠模式定義

工廠模式包括簡單工廠、工廠方法和抽象工廠三個部分金度。其中应媚,簡單工廠可以看作工廠方法的一種特例形式。

2.2 工廠模式的作用

將類的創(chuàng)建與類的使用分離猜极,職責單一中姜,減少代碼的復雜度,降低代碼的耦合性跟伏,增加代碼的可讀性和可擴展性丢胚。

  1. 封裝變化。如果邏輯有可能變化受扳,封裝成工廠類之后携龟,創(chuàng)建邏輯的變更不會影響調用者。
  2. 代碼復用勘高。創(chuàng)建代碼抽離到單獨的工廠類之后可以復用峡蟋。
  3. 隔離復雜性浮定。封裝復雜的創(chuàng)建邏輯,調用者無需知道對象是如何創(chuàng)建的层亿。
  4. 控制復雜度桦卒。將創(chuàng)建代碼抽離出來,讓原本的函數或類職責更單一匿又,代碼更簡潔方灾。

2.3 工廠模式的經典實現

簡單工廠

public class RuleConfigSource {
  public RuleConfig load(String ruleConfigFilePath) {
    String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath);
    IRuleConfigParser parser = RuleConfigParserFactory.createParser(ruleConfigFileExtension);
    if (parser == null) {
      throw new InvalidRuleConfigException(
              "Rule config file format is not supported: " + ruleConfigFilePath);
    }

    String configText = "";
    //從ruleConfigFilePath文件中讀取配置文本到configText中
    RuleConfig ruleConfig = parser.parse(configText);
    return ruleConfig;
  }

  private String getFileExtension(String filePath) {
    //...解析文件名獲取擴展名,比如rule.json碌更,返回json
    return "json";
  }
}

public class RuleConfigParserFactory {
  public static IRuleConfigParser createParser(String configFormat) {
    IRuleConfigParser parser = null;
    if ("json".equalsIgnoreCase(configFormat)) {
      parser = new JsonRuleConfigParser();
    } else if ("xml".equalsIgnoreCase(configFormat)) {
      parser = new XmlRuleConfigParser();
    } else if ("yaml".equalsIgnoreCase(configFormat)) {
      parser = new YamlRuleConfigParser();
    } else if ("properties".equalsIgnoreCase(configFormat)) {
      parser = new PropertiesRuleConfigParser();
    }
    return parser;
  }
}

特點:使用一個工廠類和一個方法來通過傳入參數的不同類型來創(chuàng)建對應的對象裕偿。


工廠方法

public interface IRuleConfigParserFactory {
  IRuleConfigParser createParser();
}

public class JsonRuleConfigParserFactory implements IRuleConfigParserFactory {
  @Override
  public IRuleConfigParser createParser() {
    return new JsonRuleConfigParser();
  }
}

public class XmlRuleConfigParserFactory implements IRuleConfigParserFactory {
  @Override
  public IRuleConfigParser createParser() {
    return new XmlRuleConfigParser();
  }
}

public class YamlRuleConfigParserFactory implements IRuleConfigParserFactory {
  @Override
  public IRuleConfigParser createParser() {
    return new YamlRuleConfigParser();
  }
}

public class PropertiesRuleConfigParserFactory implements IRuleConfigParserFactory {
  @Override
  public IRuleConfigParser createParser() {
    return new PropertiesRuleConfigParser();
  }
}

public class RuleConfigSource {
  public RuleConfig load(String ruleConfigFilePath) {
    String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath);

    IRuleConfigParserFactory parserFactory = RuleConfigParserFactoryMap.getParserFactory(ruleConfigFileExtension);
    if (parserFactory == null) {
      throw new InvalidRuleConfigException("Rule config file format is not supported: " + ruleConfigFilePath);
    }
    IRuleConfigParser parser = parserFactory.createParser();

    String configText = "";
    //從ruleConfigFilePath文件中讀取配置文本到configText中
    RuleConfig ruleConfig = parser.parse(configText);
    return ruleConfig;
  }

  private String getFileExtension(String filePath) {
    //...解析文件名獲取擴展名,比如rule.json痛单,返回json
    return "json";
  }
}

//因為工廠類只包含方法嘿棘,不包含成員變量,完全可以復用旭绒,
//不需要每次都創(chuàng)建新的工廠類對象鸟妙,所以,簡單工廠模式的第二種實現思路更加合適挥吵。
public class RuleConfigParserFactoryMap { //工廠的工廠
  private static final Map<String, IRuleConfigParserFactory> cachedFactories = new HashMap<>();

  static {
    cachedFactories.put("json", new JsonRuleConfigParserFactory());
    cachedFactories.put("xml", new XmlRuleConfigParserFactory());
    cachedFactories.put("yaml", new YamlRuleConfigParserFactory());
    cachedFactories.put("properties", new PropertiesRuleConfigParserFactory());
  }

  public static IRuleConfigParserFactory getParserFactory(String type) {
    if (type == null || type.isEmpty()) {
      return null;
    }
    IRuleConfigParserFactory parserFactory = cachedFactories.get(type.toLowerCase());
    return parserFactory;
  }
}

特點:每一個對象的創(chuàng)建都對應一個工廠類重父,然后,添加一個工廠管理類來對所有工廠來進行管理忽匈,也就是工廠的工廠房午。很多時間,每一個對象的創(chuàng)建都對應一個工廠類丹允,而工廠類中又只是創(chuàng)建一個對象郭厌,功能相對會比較單薄,直接用簡單工廠更加合適雕蔽。

抽象工廠

什么時候使用抽象工廠折柠?

如果類的分類方式大于或等于 2 時,使用抽象工廠實現方式萎羔,可以避免工廠方法實現中工廠類膨脹的問題液走。

總的來說就是讓一個工廠負責多個不同類型對象的創(chuàng)建工作。

public interface IConfigParserFactory {
  IRuleConfigParser createRuleParser();
  ISystemConfigParser createSystemParser();
  //此處可以擴展新的parser類型贾陷,比如IBizConfigParser
}

public class JsonConfigParserFactory implements IConfigParserFactory {
  @Override
  public IRuleConfigParser createRuleParser() {
    return new JsonRuleConfigParser();
  }

  @Override
  public ISystemConfigParser createSystemParser() {
    return new JsonSystemConfigParser();
  }
}

public class XmlConfigParserFactory implements IConfigParserFactory {
  @Override
  public IRuleConfigParser createRuleParser() {
    return new XmlRuleConfigParser();
  }

  @Override
  public ISystemConfigParser createSystemParser() {
    return new XmlSystemConfigParser();
  }
}

// 省略YamlConfigParserFactory和PropertiesConfigParserFactory代碼

2.4 什么時候用簡單工廠缘眶,什么時候用工廠方法,什么時候使用抽象工廠髓废?

使用簡單工廠的場景

一般需要工廠來創(chuàng)建的對象巷懈,邏輯并不復雜,通常只需要幾行代碼或者直接 new 出來就完成了創(chuàng)建慌洪。這種情況下顶燕,直接使用簡單工廠比較合適凑保。

一句話概括:對象的初始化比較簡單,通過 new 就可以搞定的情況涌攻。

使用工廠方法的場景

當對象的創(chuàng)建邏輯比較復雜欧引,不是簡單使用 new 一下就可以,而是要依賴其它的類恳谎,做各種初始化的時候芝此,推薦使用工廠方法模式。

還有一種是如果每次創(chuàng)建的對象都是不同的因痛,不需要復用的情況下婚苹,如果想避免煩人的 if-else 分支邏輯,這個時候就可以使用工廠模式實現(通過在工廠的工廠中緩存創(chuàng)建對象所對應的工廠類鸵膏,然后膊升,直接通過類型來獲取對應的工廠類,工廠類里面的實現是直接通過 new 來創(chuàng)建的)谭企。

一名話概括:對象的創(chuàng)建比較復雜廓译,需要依賴很多外部類,并且初始化過程比較復雜的情況赞咙。

使用抽象工廠的場景

如果類的分類方式大于或等于 2 時责循,使用抽象工廠實現方式糟港,可以避免工廠方法實現中工廠類膨脹的問題攀操。

總的來說就是讓一個工廠負責多個不同類型對象的創(chuàng)建工作。

一句話概括:對象的創(chuàng)建分為 2 個或以上的維度(分類)

2.5 工廠模式和 DI 容器的區(qū)別

什么是 DI 容器

DI 容器是依賴注入框架秸抚。主要作用是根據配置文件創(chuàng)建并持有一堆的對象速和。

其主要職責有:配置的解析、對象的創(chuàng)建及其生命周期的管理剥汤。

DI 容器與工廠模式的關系

DI 容器的設計思路就是基于工廠模式的颠放。DI 容器相當于一個“大工廠”,在程序啟動的時候吭敢,通過配置(要創(chuàng)建哪些類對象碰凶,要創(chuàng)建的對象依賴哪些其它類等)事先創(chuàng)建好對象。當程序需要使用某個類的某個對象時鹿驼,直接通過容器獲取即可欲低。

DI 的核心功能

  1. 配置的解析
public class RateLimiter {
  private RedisCounter redisCounter;
  public RateLimiter(RedisCounter redisCounter) {
    this.redisCounter = redisCounter;
  }
  public void test() {
    System.out.println("Hello World!");
  }
  //...
}

public class RedisCounter {
  private String ipAddress;
  private int port;
  public RedisCounter(String ipAddress, int port) {
    this.ipAddress = ipAddress;
    this.port = port;
  }
  //...
}

Spring 容器的配置文件beans.xml:
<beans>
   <bean id="rateLimiter" class="com.xzg.RateLimiter">
      <constructor-arg ref="redisCounter"/>
   </bean>
 
   <bean id="redisCounter" class="com.xzg.redisCounter">
     <constructor-arg type="String" value="127.0.0.1">
     <constructor-arg type="int" value=1234>
   </bean>
</beans>
  1. 對象的創(chuàng)建

通過一個工廠類,比如:BeanFactory畜晰,通過反射在程序運行過程中砾莱,動態(tài)地加載類并創(chuàng)建對象。不管創(chuàng)建幾個對象凄鼻,創(chuàng)建對象所對應的工廠是一樣的腊瑟。

  1. 對象的生命周期管理

DI 容器對生命周期的管理主要有對象是否是單例聚假、是否支持懶加載、配置對象創(chuàng)建時的初始化方法和對象銷毀后的銷毀方法等闰非。

DI 容器核心工廠類的設計

public class BeansFactory {
  private ConcurrentHashMap<String, Object> singletonObjects = new ConcurrentHashMap<>();
  private ConcurrentHashMap<String, BeanDefinition> beanDefinitions = new ConcurrentHashMap<>();

  public void addBeanDefinitions(List<BeanDefinition> beanDefinitionList) {
    for (BeanDefinition beanDefinition : beanDefinitionList) {
      this.beanDefinitions.putIfAbsent(beanDefinition.getId(), beanDefinition);
    }

    for (BeanDefinition beanDefinition : beanDefinitionList) {
      if (beanDefinition.isLazyInit() == false && beanDefinition.isSingleton()) {
        createBean(beanDefinition);
      }
    }
  }

  public Object getBean(String beanId) {
    BeanDefinition beanDefinition = beanDefinitions.get(beanId);
    if (beanDefinition == null) {
      throw new NoSuchBeanDefinitionException("Bean is not defined: " + beanId);
    }
    return createBean(beanDefinition);
  }

  @VisibleForTesting
  protected Object createBean(BeanDefinition beanDefinition) {
    if (beanDefinition.isSingleton() && singletonObjects.contains(beanDefinition.getId())) {
      return singletonObjects.get(beanDefinition.getId());
    }

    Object bean = null;
    try {
      Class beanClass = Class.forName(beanDefinition.getClassName());
      List<BeanDefinition.ConstructorArg> args = beanDefinition.getConstructorArgs();
      if (args.isEmpty()) {
        bean = beanClass.newInstance();
      } else {
        Class[] argClasses = new Class[args.size()];
        Object[] argObjects = new Object[args.size()];
        for (int i = 0; i < args.size(); ++i) {
          BeanDefinition.ConstructorArg arg = args.get(i);
          if (!arg.getIsRef()) {
            argClasses[i] = arg.getType();
            argObjects[i] = arg.getArg();
          } else {
            BeanDefinition refBeanDefinition = beanDefinitions.get(arg.getArg());
            if (refBeanDefinition == null) {
              throw new NoSuchBeanDefinitionException("Bean is not defined: " + arg.getArg());
            }
            argClasses[i] = Class.forName(refBeanDefinition.getClassName());
            argObjects[i] = createBean(refBeanDefinition);
          }
        }
        bean = beanClass.getConstructor(argClasses).newInstance(argObjects);
      }
    } catch (ClassNotFoundException | IllegalAccessException
            | InstantiationException | NoSuchMethodException | InvocationTargetException e) {
      throw new BeanCreationFailureException("", e);
    }

    if (bean != null && beanDefinition.isSingleton()) {
      singletonObjects.putIfAbsent(beanDefinition.getId(), bean);
      return singletonObjects.get(beanDefinition.getId());
    }
    return bean;
  }
}

其核心思想是:從配置文件中解析得到 BeanDefinition(主要包括創(chuàng)建類所必須的數據)膘格,通過 BeanId 獲取到對應的 BeanDefinition 對象,然后财松,根據 beanDefinitioin 相關的構造函數信息闯袒,通過反射來創(chuàng)建對應的對象。

3. 建造者模式

3.1 定義

建造者模式是用來創(chuàng)建一種類型的復雜對象,可以通過設置不同的可選參數,“定制化”地創(chuàng)建不同的對象游岳。

3.2 建造者模式作用

將類的創(chuàng)建與類的使用解耦政敢,并解決構造方法過長導致容易出錯的問題,或者通過構造方法和 set 方法配合使用時胚迫,可能出現的類之間的依賴或約束關系的邏輯無處安放喷户。

3.3 構造方法 + set 方法方案可能導致的問題

解決方式

將必填參數寫在構造函數中,將非必填參數使用 set 方法來提供配置性访锻。

存在問題

  1. 必填的屬性有很多褪尝,也會導致構造函數參數過長。如果將必填參數通過 set 來進行配置期犬,校驗必填屬性是否已經填寫的方式就無處安放了河哑。
  2. 如果參數之間有一定的依賴或約束關系,那這些依賴或約束關系就無處安放了龟虎。
  3. 如果需要創(chuàng)建一個不可變對象璃谨,也就是創(chuàng)建后不能改變對象的屬性,也就不能提供 set 方法了鲤妥。
  4. 這種方法可能會出現無效狀態(tài)佳吞,
Rectangle r = new Rectange(); // r is invalid 
r.setWidth(2); // r is invalid 
r.setHeight(3); // r is valid

而如果使用建造者函數則在創(chuàng)建對象之前,所有的參數已經配置完成棉安。

3.4 使用構造函數或者配合 set 方法就能創(chuàng)建對象底扳,為什么還需要建造者模式?

因為構造函數 + set 函數無法滿足四種情況贡耽。分別是:

  1. 必填構造函數參數過長問題衷模。
  2. 參數之間存在依賴或約束關系問題。
  3. 不可變對象無法提供 set 方法配置屬性問題蒲赂。
  4. 存在無效狀態(tài)問題阱冶。

3.5 與工廠模式的區(qū)別

工廠模式是用來創(chuàng)建對象不同但相關類型的對象(繼承了同一個父類,或者接口)凳宙,通過參數來決定創(chuàng)建哪一類型的對象熙揍。而建造者模式是用來創(chuàng)建同一種類型的復雜對象,通過不同的參數來定制化創(chuàng)建不同的對象氏涩。

例子

你到餐館去點餐届囚,使用工廠模式選擇不同類型的食物有梆,比如:漢堡、披薩意系,選擇了披薩后泥耀,再通過建造者模式來創(chuàng)建不同的披薩,比如:添加榴蓮蛔添、香腸痰催、起司等等。

4. 原型模式

4.1 定義

通過「復制」一個已經存在的實例來返回新的實例,而不是新建實例迎瞧。 被復制的實例就是我們所稱的「原型」夸溶,這個原型是可定制的。

4.2 原型模式的作用

如果對象的創(chuàng)建成本比較大凶硅,而同一類的不同對象之間的差異不大的情況下缝裁,可以通過直接拷貝(無需執(zhí)行對象初始化)的方式,來節(jié)省創(chuàng)建對象的時間足绅。

4.3 為何創(chuàng)建一個對象的成本比較大捷绑?

如果對象中的數據需要通過復雜的計算才能得到(比如:計算哈希值),或者需要通過 IO(如:文件氢妈,RPC粹污,數據庫等) 來進行數據的獲取。

如果直接通過拷貝其它對象得到相應的數據首量,就不需要重復執(zhí)行這些耗時的操作了壮吩。

4.3 原型模式的實現

淺拷貝

淺拷貝只會復制對象所對應的內存地址,不會復制對象本身蕾总。和原對象共享同一個對象粥航,一方改變了對象的配置,另一方也會跟著改變生百。

深拷貝

深拷貝不僅會復制對象地址,還會復制對象本身柄延。和原對象完全獨立蚀浆,互不影響。

4.4 對于基本數據類型的淺拷貝操作搜吧,對一個對象的值的改變會改變另一個對象的值么?

如果拷貝對象中只包含基本數據類型市俊,對此對象的淺拷貝操作后,一個對象的值改變之后滤奈,另一下對象的值也會跟著改變摆昧。

為什么

首先,Object 對象的 clone 是沒有實現的蜒程,需要具體的子類去實現绅你,而如果直接返回 this 的話伺帘,直接改變某一個對象中基本數據類型的值,對應的值也會跟著改變忌锯。因為基本數據類型是本質上還是一個對象伪嫁,只是被 JVM 通過自動裝箱拆箱給處理了。

對于深拷貝

一個對象進行了深拷貝后偶垮,其內部的所有基本數據類型的值张咳,都需要手動進行重新賦值給新對象,所以似舵,是相互獨立脚猾,互不影響的。

4.5 如何實現深拷貝呢砚哗?

  1. 遞歸地拷貝對象婚陪,對象的引用對象以及引用對象的引用對象,直到只剩下基本數據類型為止频祝。
  2. 先將對象序列化泌参,然后,再將數據反序列化常空。
public Object deepCopy(Object object) {
    ByteArrayOutputStream bo = new ByteArrayOutputStream(); 
    ObjectOutputStream oo = new ObjectOutputStream(bo); 
    oo.writeObject(object); 
    ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
    ObjectInputStream oi = new ObjectInputStream(bi); 
    return oi.readObject(); 
} 

應用場景

clone 散列表

假設數據庫中存儲了大約 10 萬條“搜索關鍵詞”信息沽一,每條信息包含關鍵詞、關鍵詞被搜索的次數漓糙、信息最近被更新的時間等铣缠。系統(tǒng) A 在啟動的時候會加載這份數據到內存中,用于處理某些其他的業(yè)務需求昆禽。為了方便快速地查找某個關鍵詞對應的信息蝗蛙,我們給關鍵詞建立一個散列表索引。

public class Demo { 
    private HashMap<String, SearchWord> currentKeywords=new HashMap<>(); 
    public void refresh() { 
    HashMap<String, SearchWord> newKeywords = new LinkedHashMap<>(); // 從數據庫中取出所有的數據醉鳖,放入到 newKeywords 中 
    List<SearchWord> toBeUpdatedSearchWords = getSearchWords(); 
    for (SearchWord searchWord : toBeUpdatedSearchWords) {
    newKeywords.put(searchWord.getKeyword(), searchWord); 
    } 
    currentKeywords = newKeywords; 
    } 
    private List<SearchWord> getSearchWords() { 
    // TODO: 從數據庫中取出所有的數據 return null; 
    } 
} 

在上面的使用場景中捡硅,newKeywords 對象構建的成本很高,需要將數據庫中 10 萬條數據讀取出來盗棵,并進行 hash 運算后壮韭,再構造出對象,這個過程會非常耗時纹因。

如果使用原型模式喷屋,先在內存中直接拷貝原對象,然后瞭恰,再從數據庫中取出新增或更新過了的數據屯曹,更新到 newKeywords 對象中,由于新增或更新的數據往往會比原始數據要少很多,所以恶耽,這樣做密任,大大提高了數據更新的效率。

最優(yōu)拷貝散列表的方式

先使用淺拷貝對原對象進行拷貝操作驳棱,來創(chuàng)建 newKeywords 對象批什,對于需要更新的 SearchWord 對象,我們再使用深拷貝的方式拷貝一份新的對象社搅,替換 newKeywords 中的老對象驻债。一般更新的數據整體來講會少很多,利用淺拷貝既能節(jié)省時間形葬、空間合呐,又能保證老的 currentKeywords 中保留老版本的數據。

說明

此文是根據王爭設計模式之美相關專欄內容整理而來笙以,非原創(chuàng)淌实。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市猖腕,隨后出現的幾起案子拆祈,更是在濱河造成了極大的恐慌,老刑警劉巖倘感,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件放坏,死亡現場離奇詭異,居然都是意外死亡老玛,警方通過查閱死者的電腦和手機淤年,發(fā)現死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蜡豹,“玉大人麸粮,你說我怎么就攤上這事【盗” “怎么了弄诲?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長桨吊。 經常有香客問我威根,道長,這世上最難降的妖魔是什么视乐? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮敢茁,結果婚禮上佑淀,老公的妹妹穿的比我還像新娘。我一直安慰自己彰檬,他們只是感情好伸刃,可當我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布谎砾。 她就那樣靜靜地躺著,像睡著了一般捧颅。 火紅的嫁衣襯著肌膚如雪景图。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天碉哑,我揣著相機與錄音挚币,去河邊找鬼。 笑死扣典,一個胖子當著我的面吹牛妆毕,可吹牛的內容都是我干的。 我是一名探鬼主播笛粘,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼关斜,長吁一口氣:“原來是場噩夢啊……” “哼例诀!你這毒婦竟也來了二驰?” 一聲冷哼從身側響起矿酵,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤棘捣,失蹤者是張志新(化名)和其女友劉穎测砂,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡悲没,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年栈戳,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡归薛,死狀恐怖,靈堂內的尸體忽然破棺而出匪蝙,到底是詐尸還是另有隱情主籍,我是刑警寧澤,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布逛球,位于F島的核電站千元,受9級特大地震影響,放射性物質發(fā)生泄漏颤绕。R本人自食惡果不足惜诅炉,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一蜡歹、第九天 我趴在偏房一處隱蔽的房頂上張望屋厘。 院中可真熱鬧涕烧,春花似錦、人聲如沸汗洒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽溢谤。三九已至瞻凤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間世杀,已是汗流浹背阀参。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留瞻坝,地道東北人蛛壳。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像所刀,于是被迫代替她去往敵國和親衙荐。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,724評論 2 354