創(chuàng)建型設計模式匯總
1. 單例模式
1.1 單例模式的定義
一個類只允許創(chuàng)建一個對象或實例膛锭。
1.2 單例模式的作用
- 有些數據在系統(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 單例模式的替代方案
- 靜態(tài)方法實現
- 工廠模式
- 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)建與類的使用分離猜极,職責單一中姜,減少代碼的復雜度,降低代碼的耦合性跟伏,增加代碼的可讀性和可擴展性丢胚。
- 封裝變化。如果邏輯有可能變化受扳,封裝成工廠類之后携龟,創(chuàng)建邏輯的變更不會影響調用者。
- 代碼復用勘高。創(chuàng)建代碼抽離到單獨的工廠類之后可以復用峡蟋。
- 隔離復雜性浮定。封裝復雜的創(chuàng)建邏輯,調用者無需知道對象是如何創(chuàng)建的层亿。
- 控制復雜度桦卒。將創(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 的核心功能
- 配置的解析
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>
- 對象的創(chuàng)建
通過一個工廠類,比如:BeanFactory畜晰,通過反射在程序運行過程中砾莱,動態(tài)地加載類并創(chuàng)建對象。不管創(chuàng)建幾個對象凄鼻,創(chuàng)建對象所對應的工廠是一樣的腊瑟。
- 對象的生命周期管理
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 方法來提供配置性访锻。
存在問題
- 必填的屬性有很多褪尝,也會導致構造函數參數過長。如果將必填參數通過 set 來進行配置期犬,校驗必填屬性是否已經填寫的方式就無處安放了河哑。
- 如果參數之間有一定的依賴或約束關系,那這些依賴或約束關系就無處安放了龟虎。
- 如果需要創(chuàng)建一個不可變對象璃谨,也就是創(chuàng)建后不能改變對象的屬性,也就不能提供 set 方法了鲤妥。
- 這種方法可能會出現無效狀態(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 函數無法滿足四種情況贡耽。分別是:
- 必填構造函數參數過長問題衷模。
- 參數之間存在依賴或約束關系問題。
- 不可變對象無法提供 set 方法配置屬性問題蒲赂。
- 存在無效狀態(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 如何實現深拷貝呢砚哗?
- 遞歸地拷貝對象婚陪,對象的引用對象以及引用對象的引用對象,直到只剩下基本數據類型為止频祝。
- 先將對象序列化泌参,然后,再將數據反序列化常空。
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)淌实。