vim編輯器
阿新 • • 發佈:2020-11-30
享元模式
- 享元模式(Flyweight Patterm)也叫 蠅量模式:運用共享技術有效地支援大量細粒度的物件
- 常用於系統底層開發,解決系統的效能問題。像資料庫連線池,裡面都是建立好的連線物件,在這些連線物件中有我們需要的則直接拿來用,避免重新建立,如果沒有我們需要的,則建立-一個
- 享元模式能夠解決重複物件的記憶體浪費的問題,當系統中有大量相似物件,需要緩衝池時。不需總是建立新物件,可以從緩衝池裡拿。這樣可以降低系統記憶體,同時提高效率
- 享元模式經典的應用場景就是池技術了,String 常量池、資料庫連線池、緩衝池等等都是享元模式的應用,享元模式是池技術的重要實現方式
內部狀態和外部狀態
比如圍棋、五子棋、跳棋,它們都有大量的棋子物件,圍棋和五子棋只有黑白兩色,跳棋顏色多一點,所以棋子顏色就是棋子的內部狀態;而各個棋子之間的差別就是位置的不同,當我們落子後,落子顏色是定的,但位置是變化的,所以棋子座標就是棋子的外部狀態。
- 享元模式提出了兩個要求:細粒度和共享物件。這裡就涉及到內部狀態和外部狀態了,即將物件的資訊分為兩個部分:內部狀態和外部狀態
- 內部狀態指物件共享出來的資訊,儲存在享元物件內部且不會隨環境的改變而改變
- 外部狀態指物件得以依賴的一個標記,是隨環境改變而改變的、不可共享的狀態。
應用例項
小型的外包專案,給客戶A做一個產品展示網站,客戶A的朋友感覺效果不錯,也希望做這樣的產品展示網站,但是要求都有些不同:
- 有客戶要求以新聞的形式釋出
- 有客戶人要求以部落格的形式釋出.
- 有客戶希望以微信公眾號的形式釋出
public class Client { public static void main(String[] args) { // TODO Auto-generated method stub // 建立一個工廠類 WebSiteFactory factory = new WebSiteFactory(); // 客戶要一個以新聞形式釋出的網站 WebSite webSite1 = factory.getWebSiteCategory("新聞"); webSite1.use(new User("tom")); // 客戶要一個以部落格形式釋出的網站 WebSite webSite2 = factory.getWebSiteCategory("部落格"); webSite2.use(new User("jack")); // 客戶要一個以部落格形式釋出的網站 WebSite webSite3 = factory.getWebSiteCategory("部落格"); webSite3.use(new User("smith")); // 客戶要一個以部落格形式釋出的網站 WebSite webSite4 = factory.getWebSiteCategory("部落格"); webSite4.use(new User("king")); System.out.println("網站的分類共=" + factory.getWebSiteCount()); } }
//具體網站
public class ConcreteWebSite extends WebSite {
//共享的部分,內部狀態
private String type = ""; //網站釋出的形式(型別)
//構造器
public ConcreteWebSite(String type) {
this.type = type;
}
@Override
public void use(User user) {
// TODO Auto-generated method stub
System.out.println("網站的釋出形式為:" + type + " 在使用中 .. 使用者是" + user.getName());
}
}
public class User {
private String name;
public User(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public abstract class WebSite {
public abstract void use(User user);//抽象方法
}
// 網站工廠類,根據需要返回壓一個網站
public class WebSiteFactory {
//集合, 充當池的作用
private HashMap<String, ConcreteWebSite> pool = new HashMap<>();
//根據網站的型別,返回一個網站, 如果沒有就建立一個網站,並放入到池中,並返回
public WebSite getWebSiteCategory(String type) {
if(!pool.containsKey(type)) {
//就建立一個網站,並放入到池中
pool.put(type, new ConcreteWebSite(type));
}
return (WebSite)pool.get(type);
}
//獲取網站分類的總數 (池中有多少個網站型別)
public int getWebSiteCount() {
return pool.size();
}
}
注意事項和細節
- 在享元模式這樣理解,“享” 就表示共享,“元” 表示物件
- 系統中有大量物件,這些物件消耗大量記憶體,並且物件的狀態大部分可以外部化時,我們就可以考慮選用享元模式
- 用唯一標識碼判斷,如果在記憶體中有,則返回這個唯一標識碼所標識的物件,用HashMap/HashTable儲存
- 享元模式大大減少了物件的建立,降低了程式記憶體的佔用,提高效率
- 享元模式提高了系統的複雜度。需要分離出內部狀態和外部狀態,而外部狀態具有固化特性,不應該隨著內部狀態的改變而改變,這是我們使用享元模式需要注意的地方.
- 使用享元模式時,注意劃分內部狀態和外部狀態,並且需要有一個工廠類加以控制。
- 享元模式經典的應用場景是需要緩衝池的場景,比如String 常量池、資料庫連線池
代理模式
- 代理模式:為一個物件提供一個替身,以控制對這個物件的訪問。即通過代理物件訪問目標物件,這樣做的好處是:可以在目標物件實現的基礎上,增強額外的功能操作,即擴充套件目標物件的功能。
- 被代理的物件可以是遠端物件、建立開銷大的物件或需要安全控制的物件
- 代理模式有不同的形式,主要有三種靜態代理、動態代理(JDK代理、介面代理)和Cglib 代理(可以在記憶體動態的建立物件,而不需要實現介面,他是 屬於動態代理的範疇)。
靜態代理
- 定義一個介面:ITeacherDao
- 目標物件TeacherDAO實現介面ITeacherDAO
- 使用靜態代理方式,就需要在代理物件TeacherDAOProxy中也實現ITeacherDAO
- 呼叫的時候通過呼叫代理物件的方法來呼叫目標物件.
- 特別提醒:代理物件與目標物件要實現相同的介面,然後通過呼叫相同的方法來呼叫目標物件的方法
public class Client {
public static void main(String[] args) {
// TODO Auto-generated method stub
//建立目標物件(被代理物件)
TeacherDao teacherDao = new TeacherDao();
//建立代理物件, 同時將被代理物件傳遞給代理物件
TeacherDaoProxy teacherDaoProxy = new TeacherDaoProxy(teacherDao);
//通過代理物件,呼叫到被代理物件的方法
//即:執行的是代理物件的方法,代理物件再去呼叫目標物件的方法
teacherDaoProxy.teach();
}
}
//介面
public interface ITeacherDao {
void teach(); // 授課的方法
}
public class TeacherDao implements ITeacherDao {
@Override
public void teach() {
// TODO Auto-generated method stub
System.out.println(" 老師授課中 。。。。。");
}
}
//代理物件,靜態代理
public class TeacherDaoProxy implements ITeacherDao{
private ITeacherDao target; // 目標物件,通過介面來聚合
//構造器
public TeacherDaoProxy(ITeacherDao target) {
this.target = target;
}
@Override
public void teach() {
// TODO Auto-generated method stub
System.out.println("開始代理 完成某些操作。。。。。 ");//方法
target.teach();
System.out.println("提交。。。。。");//方法
}
}
靜態代理優缺點
- 優點:在不修改目標物件的功能前提下,能通過代理物件對目標功能擴充套件
- 缺點:因為代理物件需要與目標物件實現一樣的介面,所以會有很多代理類
- 一旦介面增加方法,目標物件與代理物件都要維護
動態代理
- 代理物件,不需要實現介面,但是目標物件要實現介面,否則不能用動態代理
- 代理物件的生成,是利用JDK的API,動態的在記憶體中構建代理物件
- 動態代理也叫做: JDK 代理、介面代理
JDK中生成代理物件的API
- 代理類所在包:java.lang.reflet.Proxy
- JDK實現代理只需要使用newProxyInstance 方法,但是該方法需要接收三個引數,完整的寫法是:
static Object newProxyInstance(ClassLoader loader, Class <?>[] interfaces,InvocationHandler h )
public class Client {
public static void main(String[] args) {
// TODO Auto-generated method stub
//建立目標物件
ITeacherDao target = new TeacherDao();
//給目標物件,建立代理物件, 可以轉成 ITeacherDao
ITeacherDao proxyInstance = (ITeacherDao)new ProxyFactory(target).getProxyInstance();
// proxyInstance=class com.sun.proxy.$Proxy0 記憶體中動態生成了代理物件
System.out.println("proxyInstance=" + proxyInstance.getClass());
//通過代理物件,呼叫目標物件的方法
//proxyInstance.teach();
proxyInstance.sayHello(" tom ");
}
}
//介面
public interface ITeacherDao {
void teach(); // 授課方法
void sayHello(String name);
}
public class ProxyFactory {
//維護一個目標物件 , Object
private Object target;
//構造器 , 對target 進行初始化
public ProxyFactory(Object target) {
this.target = target;
}
//給目標物件 生成一個代理物件
public Object getProxyInstance() {
//說明
/*
* public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
//1. ClassLoader loader : 指定當前目標物件使用的類載入器, 獲取載入器的方法固定
//2. Class<?>[] interfaces: 目標物件實現的介面型別,使用泛型方法確認型別
//3. InvocationHandler h : 事情處理,執行目標物件的方法時,會觸發事情處理器方法, 會把當前執行的目標物件方法作為引數傳入
*/
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// TODO Auto-generated method stub
System.out.println("JDK代理開始~~");
//反射機制呼叫目標物件的方法
Object returnVal = method.invoke(target, args);
System.out.println("JDK代理提交");
return returnVal;
}
});
}
}
public class TeacherDao implements ITeacherDao {
@Override
public void teach() {
// TODO Auto-generated method stub
System.out.println(" 老師授課中.... ");
}
@Override
public void sayHello(String name) {
// TODO Auto-generated method stub
System.out.println("hello " + name);
}
}
Cglib代理
- 靜態代理和JDK代理模式都要求目標物件是實現一個介面,但是有時候目標物件只是一個單獨的物件,並沒有實現任何的介面,這個時候可使用目標物件子類來實現代理這就是Cglib代理
- Cglib代理也叫作子類代理,它是在記憶體中構建一個子類物件從而實現對目標物件功能擴充套件,有些書也將Cglib代理歸屬到動態代理。
- Cglib是一個強大的高效能的程式碼生成包,它可以在執行期擴充套件java類與實現java介面.它廣泛的被許多AOP的框架使用,例如Spring AOP,實現方法攔截
- 在AOP程式設計中如何選擇代理模式:
- 目標物件需要實現介面,用JDK代理
- 目標物件不需要實現介面,用Cglib代理
- Cglib包的底層是通過使用位元組碼處理框架ASM來轉換位元組碼並生成新的類
Cglib代理模式實現步驟
-
匯入Cglib的jar包
asm.jar
asm-commons.jar
asm-tree.jar
cglib-2.2.jar -
在記憶體中動態建立子類,注意代理的類不能為final,否則報錯。
-
目標物件的方法如果為final/static,那麼不會被攔截,即不會執行目標物件額外的業務方法。
Cglib代理應用例項
public class Client {
public static void main(String[] args) {
// TODO Auto-generated method stub
//建立目標物件
TeacherDao target = new TeacherDao();
//獲取到代理物件,並且將目標物件傳遞給代理物件
TeacherDao proxyInstance = (TeacherDao)new ProxyFactory(target).getProxyInstance();
//執行代理物件的方法,觸發intecept 方法,從而實現 對目標物件的呼叫
String res = proxyInstance.teach();
System.out.println("res=" + res);
}
}
public class ProxyFactory implements MethodInterceptor {
//維護一個目標物件
private Object target;
//構造器,傳入一個被代理的物件
public ProxyFactory(Object target) {
this.target = target;
}
//返回一個代理物件: 是 target 物件的代理物件
public Object getProxyInstance() {
//1. 建立一個工具類
Enhancer enhancer = new Enhancer();
//2. 設定父類
enhancer.setSuperclass(target.getClass());
//3. 設定回撥函式
enhancer.setCallback(this);
//4. 建立子類物件,即代理物件
return enhancer.create();
}
//重寫 intercept 方法,會呼叫目標物件的方法
@Override
public Object intercept(Object arg0, Method method, Object[] args, MethodProxy arg3) throws Throwable {
// TODO Auto-generated method stub
System.out.println("Cglib代理模式 ~~ 開始");
Object returnVal = method.invoke(target, args);
System.out.println("Cglib代理模式 ~~ 提交");
return returnVal;
}
}
public class TeacherDao {
public String teach() {
System.out.println(" 老師授課中 , 我是cglib代理,不需要實現介面 ");
return "hello";
}
}
幾種常見的代理模式介紹一幾種變體
- 防火牆代理
內網通過代理穿透防火牆,實現對公網的訪問。 - 快取代理
比如:當請求圖片檔案等資源時,先到快取代理取,如果取到資源則ok,如果取不到資源,再到公網或者資料庫取,然後快取。 - 遠端代理
遠端物件的本地代表,通過它可以把遠端物件當本地物件來呼叫。遠端代理通過網路和真正的遠端物件溝通訊息。 - 同步代理:主要使用在多執行緒程式設計中,完成多執行緒間同步工作