加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 综合聚焦 > 服务器 > 安全 > 正文

Facade模式 和 Proxy模式

发布时间:2020-12-17 01:05:11 所属栏目:安全 来源:网络整理
导读:?Facade简化并提供了对基础架构的统一访问,从而减少复杂性。它只是封装,可以看成黑盒。 在Web编程中,有所谓的n-层架构,就是Facade的思想,即每一层都封装好一部分功能,提供给上一层统一的方法调用,比如说数据层,将操作一次数据库的整个过程封装为一个
?Facade简化并提供了对基础架构的统一访问,从而减少复杂性。它只是封装,可以看成黑盒。
在Web编程中,有所谓的n-层架构,就是Facade的思想,即每一层都封装好一部分功能,提供给上一层统一的方法调用,比如说数据层,将操作一次数据库的整个过程封装为一个方法,而我们在逻辑层调用该方法时,只需要传递一个SQL参数:
<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

-->????????public?static?void?ExecNonQuery(string
?sql)
????????{?
????????????
using?(SqlConnection?conn?=?new
?SqlConnection())
????????????{
????????????????conn.Open();

????????????????SqlCommand?cmd?
=?new
?SqlCommand(sql);
????????????????cmd.ExecuteNonQuery();????????????????
????????????}
????????}


整个Framework体系就是Facade模式的封装,随着1.0升级到3.5,越来越多复杂的高级功能被封装,可以说Facade无处不在。
设计Facade时,要尽量松散耦和。可以把子功能分别设计为Facade,再将这些小Facade拼装成一个大的Facade,而不是把这些子功能的代码直接拼成一个Facade

?

?

?

?

Facade的定義: 爲子系統中的一組介面提供一個一致的介面。

Facade一個典型應用就是資料庫JDBC的應用,如下例對資料庫的操作:

public class DBCompare {

  Connection conn = null;
  PreparedStatement prep = null;
  ResultSet rset = null;
  try {
     Class.forName( "<driver>" ).newInstance();
     conn = DriverManager.getConnection( "<database>" );
    
     String sql = "SELECT * FROM <table> WHERE <column name> = ?";
     prep = conn.prepareStatement( sql );
     prep.setString( 1,"<column value>" );
     rset = prep.executeQuery();
     if( rset.next() ) {
        System.out.println( rset.getString( "<column name" ) );
     }
  } catch( SException e ) {
     e.printStackTrace();
  } finally {
     rset.close();
     prep.close();
     conn.close();
  }
}

上例是Jsp中最通常的對資料庫操作辦法。

在應用中,經常需要對資料庫操作,每次都寫上述一段代碼肯定比較麻煩,需要將其中不變的部分提煉出來,做成一個介面,這就引入了facade外觀物件。如果以後我們更換Class.forName中的<driver>也非常方便,比如從Mysql資料庫換到Oracle資料庫,只要更換facade介面中的driver就可以。

我們做成了一個Facade介面,使用該介面,上例中的程式就可以更改如下:

public class DBCompare {

  String sql = "SELECT * FROM <table> WHERE <column name> = ?";  

  try {
     Mysql msql=new mysql(sql);
     prep.setString( 1,"<column value>" );
     rset = prep.executeQuery();
     if( rset.next() ) {
        System.out.println( rset.getString( "<column name" ) );
     }
  } catch( SException e ) {
     e.printStackTrace();
  } finally {
     mysql.close();
     mysql=null;
  }
}

可見非常簡單,所有程式對資料庫訪問都是使用改介面,降低系統的複雜性,增加了靈活性。

如果我們要使用連接池,也只要針對facade介面修改就可以。

?

由上圖可以看出,facade實際上是個理順系統間關係,降低系統間耦合度的一個常用的辦法,也許你已經不知不覺在使用,儘管不知道它就是facade。

?

?

?

?

?

?

?

?

?

?

?

?

?

Proxy :-----------------------------------------------

?

?

?

这个模式用途很多,先看它的UML:


?

????abstract?public?class
?Subject

????

{

????????
abstract?public?void
?Request();

????}


????
public?class ?RealSubject?:?Subject

????

{

????????
public?override?void
?Request()

????????

{

????????????
//Do?Something

????????}

????}


????
public?class ?Proxy?:?Subject

????

{

????????
private
?RealSubject?realSubject;


????????
public?override?void
?Request()

????????

{

????????????
//关键是这句话

????????????realSubject.Request();

????????}

????}

?

?Client端使用如下:
??????????? Subject subject = new Proxy();
??????????? subject.Request();

Proxy有以下用途:
1.远程代理?????Remoting和WebService都是基于此技术,这里就不多说了。
2.虚代理????????单元测试中的Mock技术,以及加载大图片/处理大对象时的等待过程
????Mock技术:如果现在的RealSubject类的Request()方法还没写好,可以继承Subject基类生成Proxy,这是Proxy类的Request()方法这么写:

?

????????public?override?void
?Request()

????????

{

????????????
//从配置文件中读取是否出于测试状态

????????????bool?isTest?=?GetConfiguration("isText");


????????????
if
?(isTest)

????????????

{

????????????????
//写一些期望的假操作

????????????}

????????????
else

????????????

{

????????????????realSubject.Request();

????????????}

????????}

?

????加载大图片:

????public?interface
?Imager

????

{

????????Image?getImage();

????}


????
public?class ?LargeImage?:?Imager

????

{

????????
public
?Image?getImage()

????????

{

????????????
return?new?Bitmap(@"BigImage.jpg"
);?;

????????}

????}


????
public?class ?ProxyImage?:?Imager

????

{

????????
private
?Timer?tm;

????????
private?bool
?isReady;

????????
private
?LargeImage?largeImage;

????????

????????
public
?ProxyImage()

????????

{

????????????isReady?
=?false;


????????????tm?
=?new?Timer(

????????????????
delegate

????????????????

{

????????????????????isReady?
=?true
;

????????????????????tm.Dispose();

????????????????}
,

????????????????
this,?10000,?0
);

????????}

????????

????????
public?Image?getImage()

????????

{

????????????
if
?(isReady)

????????????

{????????????????

????????????????
return
?largeImage.getImage();

????????????}

????????????
else

????????????

{

????????????????
return?new?Bitmap("SmallImage.gif"
);

????????????}

????????}

????}

?

在Client端调用方法:

?

????????private
?ProxyImage?imgProxy;


????????
private?void?Form1_Load(object
?sender,?EventArgs?e)

????????

{

????????????imgProxy?
=?new
?ProxyImage();

????????}


????????
private?void?btnLoad_Click(object ?sender,?EventArgs?e)

????????

{

????????????
//Pic为一个PictureBox控件

????????????Pic.Image?=?imgProxy.getImage();

????????}

?

注意这句话:????????????
??????? tm = new Timer(
??????????????? delegate
??????????????? {
??????????????????? isReady = true;
??????????????????? tm.Dispose();
??????????????? },
??????????????? this,10000,0);
关键就是这句话,开始isReady为false,加载小图片。10秒钟后才将isReady设置为true,并使Timer失效,从而再次调用ProxyImage的getImage()方法,加载大图片。

3.保护代理???? 控制对原始对象的访问,如增加/去除权限

原有系统的方法GetDisCount(),只有Role=管理员时才可以调用。

?

????public?class
?CompDiscount

????

{

????????
private?string
?role;


????????
public?string
?Role

????????

{

????????????
get?

{?return?role;?}

????????????
set?

{?role?=?value;?}

????????}


????????
public?Double?GetDiscount()

????????

{

????????????
if?(role?==?"admin"
)

????????????

{

????????????????
return?1000
;

????????????}

????????????
else

????????????

{

????????????????
throw?new
?Exception();

????????????}

????????}

????}

?

现在要直接使用这个方法,而跳过权限控制,可以从中派生出一个子类ProxyCompDiscount:



????public?class
?ProxyCompDiscount?:?CompDiscount

????

{

????????
private
?CompDiscount?c;


????????
public
?ProxyCompDiscount()

????????

{

????????????c?
=?new
?CompDiscount();

????????????c.Role?
=?"admin"
;

????????}


????????
public?new?Double?GetDiscount()

????????

{

????????????
return
?c.GetDiscount();

????????}

????}

?

以上是去除多余的权限,相当于短路操作。反之,也可以使用同样的方法,增加权限。

4.智能指引???? 在访问对象时执行一些附加的操作,如强制类型集


?

????public?class
?Field?

????

{

????????
private?string
?fieldName;


????????
public?Field(string
?fieldName)

????????

{

????????????
this.fieldName?=
?fieldName;

????????}

????}


????
public?class ?FieldCollection?:?CollectionBase

????

{

????????
public?int
?Add(Field?field)

????????

{

????????????
return
?InnerList.Add(field);

????????}


????????
public?Field?Add(string?fieldName)

????????

{

????????????Field?field?
=?new
?Field(fieldName);

????????????Add(field);


????????????
return
?field;

????????}


????????
public?void?Add(params?string[]?fields)

????????

{

????????????
foreach?(string?field?in
?fields)

????????????

{

????????????????Add(field);

????????????}

????????}


????????
public?Field?this[int?index]

????????

{

????????????
get
?

????????????

{

????????????????
return
?(Field)InnerList[index];

????????????}

????????}


????????
public?void?Remove(Field?field)

????????

{

????????????InnerList.Remove(field);

????????}

????}

?

在Client端,Proxy模式不用知道被代理的对象;而Decorator模式需要知道被修饰的对象。这是二者的区别

?

?

?

?

?

?

?

?

?

?

?

?

?

理解並使用設計模式,能夠培養我們良好的面向物件編程習慣,同時在實際應用中,可以如魚得水,享受遊刃有餘的樂趣。

Proxy是比較有用途的一種模式,而且變種較多,應用場合覆蓋從小結構到整個系統的大結構,Proxy是代理的意思,我們也許有代理伺服器等概念,代理概念可以解釋爲:在出發點到目的地之間有一道中間層,意爲代理。

設計模式中定義: 爲其他物件提供一種代理以控制對這個物件的訪問。

爲什麽要使用Proxy?
1.授權機制 不同級別的用戶對同一物件擁有不同的訪問權利,如Jive論壇系統中,就使用Proxy進行授權機制控制,訪問論壇有兩種人:註冊用戶和遊客(未註冊用戶),Jive中就通過類似ForumProxy這樣的代理來控制這兩種用戶對論壇的訪問許可權。

2.某個用戶端不能直接操作到某個物件,但又必須和那個物件有所互動。
舉例兩個具體情況:
(1)如果那個物件是一個是很大的圖片,需要花費很長時間才能顯示出來,那麽當這個圖片包含在文檔中時,使用編輯器或瀏覽器打開這個文檔,打開文檔必須很迅速,不能等待大圖片處理完成,這時需要做個圖片Proxy來代替真正的圖片。

(2)如果那個物件在Internet的某個遠端伺服器上,直接操作這個物件因爲網路速度原因可能比較慢,那我們可以先用Proxy來代替那個物件。

總之原則是,對於開銷很大的物件,只有在使用它時才創建,這個原則可以爲我們節省很多寶貴的Java記憶體。 所以,有些人認爲Java耗費資源記憶體,我以爲這和程式編制思路也有一定的關係。

如何使用Proxy?
以Jive論壇系統爲例,訪問論壇系統的用戶有多種類型:註冊普通用戶 論壇管理者 系統管理者 遊客,註冊普通用戶才能發言;論壇管理者可以管理他被授權的論壇;系統管理者可以管理所有事務等,這些許可權劃分和管理是使用Proxy完成的。

Forum是Jive的核心介面,在Forum中陳列了有關論壇操作的主要行爲,如論壇名稱 論壇描述的獲取和修改,帖子發表刪除編輯等。

在ForumPermissions中定義了各種級別許可權的用戶:

public class ForumPermissions implements Cacheable {

/**
* Permission to read object.
*/
public static final int READ = 0;

/**
* Permission to administer the entire sytem.
*/
public static final int SYSTEM_ADMIN = 1;

/**
* Permission to administer a particular forum.
*/
public static final int FORUM_ADMIN = 2;

/**
* Permission to administer a particular user.
*/
public static final int USER_ADMIN = 3;

/**
* Permission to administer a particular group.
*/
public static final int GROUP_ADMIN = 4;

/**
* Permission to moderate threads.
*/
public static final int MODERATE_THREADS = 5;

/**
* Permission to create a new thread.
*/
public static final int CREATE_THREAD = 6;

/**
* Permission to create a new message.
*/
public static final int CREATE_MESSAGE = 7;

/**
* Permission to moderate messages.
*/
public static final int MODERATE_MESSAGES = 8;

.....

public boolean isSystemOrForumAdmin() {
  return (values[FORUM_ADMIN] || values[SYSTEM_ADMIN]);
}

.....

}

因此,Forum中各種操作許可權是和ForumPermissions定義的用戶級別有關係的,作爲介面Forum的實現:ForumProxy正是將這種對應關係聯繫起來。比如,修改Forum的名稱,只有論壇管理者或系統管理者可以修改,代碼如下:

public class ForumProxy implements Forum {

private ForumPermissions permissions;
private Forum forum;
this.authorization = authorization;

public ForumProxy(Forum forum,Authorization authorization,
ForumPermissions permissions)
{
this.forum = forum;
this.authorization = authorization;
this.permissions = permissions;
}

.....

public void setName(String name) throws UnauthorizedException,
ForumAlreadyExistsException
{
  //只有是系統或論壇管理者才可以修改名稱
  if (permissions.isSystemOrForumAdmin()) {
    forum.setName(name);
  }
  else {
    throw new UnauthorizedException();
  }
}

...

}

而DbForum才是介面Forum的真正實現,以修改論壇名稱爲例:

public class DbForum implements Forum,Cacheable {
...

public void setName(String name) throws ForumAlreadyExistsException {

  ....

  this.name = name;
  //這裏真正將新名稱保存到資料庫中
  saveToDb();

  ....
}


...

}

凡是涉及到對論壇名稱修改這一事件,其他程式都首先得和ForumProxy打交道,由ForumProxy決定是否有許可權做某一樣事情,ForumProxy是個名副其實的"閘道","安全代理系統"。

在平時應用中,無可避免總要涉及到系統的授權或安全體系,不管你有無意識的使用Proxy,實際你已經在使用Proxy了。

我們繼續結合Jive談入深一點,下面要涉及到工廠模式了,如果你不瞭解工廠模式,請看我的另外一篇文章:設計模式之Factory

我們已經知道,使用Forum需要通過ForumProxy,Jive中創建一個Forum是使用Factory模式,有一個總的抽象類別ForumFactory,在這個抽象類別中,呼叫ForumFactory是通過getInstance()方法實現,這裏使用了Singleton(也是設計模式之一,由於介紹文章很多,我就不寫了,看這裏),getInstance()返回的是ForumFactoryProxy。

爲什麽不返回ForumFactory,而返回ForumFactory的實現ForumFactoryProxy?
原因是明顯的,需要通過代理確定是否有許可權創建forum。

在ForumFactoryProxy中我們看到代碼如下:

public class ForumFactoryProxy extends ForumFactory {

  protected ForumFactory factory;
  protected Authorization authorization;
  protected ForumPermissions permissions;

  public ForumFactoryProxy(Authorization authorization,ForumFactory factory,
  ForumPermissions permissions)
  {
    this.factory = factory;
    this.authorization = authorization;
    this.permissions = permissions;
  }

  public Forum createForum(String name,String description)
      throws UnauthorizedException,ForumAlreadyExistsException
  {
    //只有系統管理者才可以創建forum
    if (permissions.get(ForumPermissions.SYSTEM_ADMIN)) {
      Forum newForum = factory.createForum(name,description);
      return new ForumProxy(newForum,authorization,permissions);
    }
    else {
      throw new UnauthorizedException();
  }
}

方法createForum返回的也是ForumProxy,Proxy就象一道牆,其他程式只能和Proxy交互操作。

注意到這裏有兩個Proxy:ForumProxy和ForumFactoryProxy。 代表兩個不同的職責:使用Forum和創建Forum;
至於爲什麽將使用物件和創建物件分開,這也是爲什麽使用Factory模式的原因所在:是爲了"封裝" "分派";換句話說,盡可能功能單一化,方便維護修改。

Jive論壇系統中其他如帖子的創建和使用,都是按照Forum這個思路而來的。

以上我們討論了如何使用Proxy進行授權機制的訪問,Proxy還可以對用戶隱藏另外一種稱爲copy-on-write的優化方式。拷貝一個龐大而複雜的物件是一個開銷很大的操作,如果拷貝過程中,沒有對原來的物件有所修改,那麽這樣的拷貝開銷就沒有必要。用代理延遲這一拷貝過程。

比如:我們有一個很大的Collection,具體如hashtable,有很多用戶端會並發同時訪問它。其中一個特別的用戶端要進行連續的資料獲取,此時要求其他用戶端不能再向hashtable中增加或刪除 東東。

最直接的解決方案是:使用collection的lock,讓這特別的用戶端獲得這個lock,進行連續的資料獲取,然後再釋放lock。
public void foFetches(Hashtable ht){
  synchronized(ht){
    //具體的連續資料獲取動作。。
  }

}

但是這一辦法可能鎖住Collection會很長時間,這段時間,其他用戶端就不能訪問該Collection了。

第二個解決方案是clone這個Collection,然後讓連續的資料獲取針對clone出來的那個Collection操作。這個方案前提是,這個Collection是可clone的,而且必須有提供深度clone的方法。Hashtable就提供了對自己的clone方法,但不是Key和value物件的clone,關於Clone含義可以參考專門文章。
public void foFetches(Hashtable ht){

  Hashttable newht=(Hashtable)ht.clone();

}

問題又來了,由於是針對clone出來的物件操作,如果原來的母體被其他用戶端操作修改了,那麽對clone出來的物件操作就沒有意義了。

最後解決方案:我們可以等其他用戶端修改完成後再進行clone,也就是說,這個特別的用戶端先通過呼叫一個叫clone的方法來進行一系列資料獲取操作。但實際上沒有真正的進行物件拷貝,直至有其他用戶端修改了這個物件Collection。

使用Proxy實現這個方案。這就是copy-on-write操作。

Proxy應用範圍很廣,現在流行的分佈計算方式RMI和Corba等都是Proxy模式的應用。

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读