OpenAI 的 Sora 模型面世之后,可以說人類抵御AI的最后陣地也淪陷了。
在此之前,人們面對AI交互式對話,AI制圖,AI建模之類的奇跡時,還可以略微放肆的說:“的確很神奇,這畢竟還是比人類世界低了一個維度,并沒有那么可怕。”
而現在,視頻生成模型Sora在發展初期創造的視頻就幾乎可以以假亂真了,這對人類來說究竟意味著什么?
也許大多數人并沒有想清楚。
AI可以生成視頻,這意味著大模型已經完全理解了人類所在的三維世界的一切物理規則和絕大部分的社會規則。它正在變得比這個世界上的大多數人都更了解這個世界。
AI創造的視頻中的各種“生命體”在現實世界中是不存在的,如果他們有所謂的“意識”真的可以稱之為“生命”的話。他們的世界是我們世界的投影,在那個世界里“生活”的“人”其實并不清楚他們的世界是怎么來的,就像現在的我們一樣。
就像電影《異次元駭客》所描述的一樣,每個世界都是更高一級世界的投影,每個人都是NPC,每當上層世界的玩家“登錄”,下層的意識就會被搶占。所以有些人會突然發神經仿佛人格分裂一樣,就好比GTA5中的老麥在你打開游戲前是一個好父親,而在你打開游戲后變成了砍天砍地的惡棍。時不時他還會冒出來一句:“Oh, my goodness, what have I done?!”
幻想總是能讓人思緒飛揚,今天我想從“奪舍”這個角度來講一講設計模式中的享元模式。
一言
享元模式,旨在通過共享對象來減少內存使用量并提高性能。它適用于那些由于對象內部狀態重復而導致大量內存消耗的場景。
對下一層世界的秩序設計
我們假定你的幾行代碼就可以設計出一個比你低一個層次的世界,那么你打算如何實現“每個人都是NPC,每當上層世界的玩家登錄,下層的意識就會被搶占”這一需求呢?
核心代碼Ctrl CV?
“欸,我直接copy走起”,相信大家第一時間想到的都是這個設計。
的確是通俗易懂,傳統的定制化編程實現,但也確實存在很多的隱患。
分析
幾個玩家操控的NPC相似度這么高,如果分多個虛擬空間來處理,相當于一個NPC占用了多個實例,造成了極大的內存浪費。
那么有沒有一種可能,我們將這些NPC實例整合到一個NPC中(讓他擁有多個人格),對于硬盤、內存、CPU、數據庫空間等服務器資源都可以達成共享,是不是就減少了服務器資源的浪費呢?
對于代碼而言,我們也后期也只需要維護和擴展一份,這樣的思路是不是更好呢?
享元模式
也許乍一聽這個名詞很多朋友會一愣,覺得自己從未接觸過這個設計模式,實際上它非常的常見。比如說數據庫連接池的設計,里面都是創建好的連接對象,在這些連接對象中有我們需要的就直接拿來用,沒有就創建一個。
在這種思路下,解決了重復對象的內存浪費問題,當系統中存在大量相似對象,需要緩沖池時,不再需要創建新對象,而是直接在緩沖池里拿。
不光是數據庫連接池,String 常量池 ,緩沖池等等都是享元模式的應用,也是池技術的重要實現方式。
設計
- FlyWeight 是抽象的享元角色,它是產品的抽象類,同時定義出對象的外部狀態和內部狀態的接口或實現;
- ConcreteFlyWeight 是具體的享元角色,是具體的產品類,實現抽象角色自定義相關業務;
- UnsharedConcreteFlyWeight 是不可共享的角色,一般不會出現在享元工廠中;
- FlyWeightFactory 享元工廠類,用于構建一個池容器(集合),同時提供從池中獲取對象的方法;
內部狀態與外部狀態
享元模式有兩個要求:細粒度和共享對象。這里就涉及到內部狀態和外部狀態了。那么什么是內部狀態和外部狀態呢?
通俗的講,大家都玩過王者榮耀或者英雄聯盟這類的MOBA游戲,召喚師峽谷的地圖信息和地圖資源是基本固定的,而每局游戲參戰的英雄,英雄的位置、狀態、技能等信息則是千變萬化的。在這個例子中:
- 地圖是內部狀態,地圖上野怪的屬性和狀態是外部狀態
- 英雄模型是內部狀態,英雄的血量、等級等變化的是外部狀態
內部狀態指對象共享出來的信息,存儲在享元對象內部且不會隨環境的改變而改變。外部狀態指對象得以依賴的一個標記,是隨環境改變而改變的、不可共享的狀態。
可以試想以下,如果不采用享元模式,類似的游戲(PUBG、棋牌類游戲等)每一局都構建一整套資源有多么恐怖。
代碼實現
現在我們開始編輯對NPC人格的入侵程序:
玩家
public class User {private String name;public User(String name) {this.name = name;}public String getName() {return name;}public void setName(String name) {this.name = name;}
}
NPC
public abstract class NPC {public abstract void use(User user);//抽象方法
}
NPC享元角色
public class ConcreteNPC extends NPC{private String type = "";public ConcreteNPC(String type) {this.type = type;}@Overridepublic void use(User user) {System.out.println("NPC的人格切換為:"+type+",在使用中.. 使用者為:"+user.getName());}
}
NPC享元工廠
public class NPCFactory {private HashMap<String,ConcreteNPC> pool = new HashMap<>();public NPC getNpcCategory(String type){if (!pool.containsKey(type)){pool.put(type,new ConcreteNPC(type));}return (NPC) pool.get(type);}public int getNpcCount(){return pool.size();}
}
客戶端
public class Client {public static void main(String[] args) {NPCFactory factory = new NPCFactory();NPC npc1 = factory.getNpcCategory("圣人");npc1.use(new User("Tom"));NPC npc2 = factory.getNpcCategory("狂徒");npc2.use(new User("Jack"));NPC npc3 = factory.getNpcCategory("學者");npc3.use(new User("Linda"));NPC npc4 = factory.getNpcCategory("學者");npc4.use(new User("Lucy"));NPC npc5 = factory.getNpcCategory("學者");npc5.use(new User("Amanda"));System.out.println("NPC人格分類共:"+factory.getNpcCount());}
}
執行
可以看到,在當前的設計下,NPC的人格集中管理在享元工廠的池子中,當有新的玩家注入新的人格則會擴充這個池子的容量,如果沒有新的人格加入,則會從池子中提取已有的人格注入到NPC體內。
享元模式在JDK-Integer源碼中的應用
之前網上有這樣一種論調:“國內IT行業程序員的面試已經越來越朝向八股化發展了。
面試造火箭,工作打螺絲”。
從某種角度來看,似乎說的是行業面試的現狀。但從編程基礎的角度考慮,有些八股本身不是問題,問題是很多朋友沒有真正理解八股描述的底層原理,全靠死記硬背。
比如說下面這個面試題:
Integer a = Integer.valueOf(127);Integer b = new Integer(127);Integer c = Integer.valueOf(127);Integer d = new Integer(127);System.out.println(a.equals(b));System.out.println(a==b);System.out.println(a==c);System.out.println(d==a);System.out.println(d==b);
八股文只會告訴你,上面的結果是:
但我覺得這不是我們要去背的東西,我們要理解為什么是這樣的結果。這個結果其實就源自Integer的享元模式,我們先看下源碼。
相關源碼片
/*** Cache to support the object identity semantics of autoboxing for values between* -128 and 127 (inclusive) as required by JLS.** The cache is initialized on first usage. The size of the cache* may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option.* During VM initialization, java.lang.Integer.IntegerCache.high property* may be set and saved in the private system properties in the* sun.misc.VM class.*/private static class IntegerCache {static final int low = -128;static final int high;static final Integer cache[];static {// high value may be configured by propertyint h = 127;String integerCacheHighPropValue =sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");if (integerCacheHighPropValue != null) {try {int i = parseInt(integerCacheHighPropValue);i = Math.max(i, 127);// Maximum array size is Integer.MAX_VALUEh = Math.min(i, Integer.MAX_VALUE - (-low) -1);} catch( NumberFormatException nfe) {// If the property cannot be parsed into an int, ignore it.}}high = h;cache = new Integer[(high - low) + 1];int j = low;for(int k = 0; k < cache.length; k++)cache[k] = new Integer(j++);// range [-128, 127] must be interned (JLS7 5.1.7)assert IntegerCache.high >= 127;}private IntegerCache() {}}/*** Returns an {@code Integer} instance representing the specified* {@code int} value. If a new {@code Integer} instance is not* required, this method should generally be used in preference to* the constructor {@link #Integer(int)}, as this method is likely* to yield significantly better space and time performance by* caching frequently requested values.** This method will always cache values in the range -128 to 127,* inclusive, and may cache other values outside of this range.** @param i an {@code int} value.* @return an {@code Integer} instance representing {@code i}.* @since 1.5*/public static Integer valueOf(int i) {if (i >= IntegerCache.low && i <= IntegerCache.high)return IntegerCache.cache[i + (-IntegerCache.low)];return new Integer(i);}
源碼分析
- 在valueOf方法中,先判斷值是否在IntegerCache中,如果不在,就創建新的Integer(new),否則,就直接從緩存池返回;
- valueOf方法就使用到了享元模式;
- 如果使用valueOf方法得到一個Integer實例范圍在-128~127之間,執行速度比 new 快;
所以,以-128~127為界,比較Integer是否是同一個對象時會有不同的表現結果。
結
“享”即共享,“元”即對象,如果系統中有大量對象占用緩存,并且對象狀態大都可以外部化時,我們就可以考慮選用享元模式。
但是也要清楚的看到,享元模式在提高了效率的同時也提高了系統復雜度,而且,外部狀態具有固化特性不會隨著內部狀態的改變而改變,這也會在一定程度上增加編碼邏輯的理解難度。
關注我,共同進步,每周至少一更。——Wayne