DES稱之為Data Encryption Standard, 屬於基礎的單方向性服務. 可以拿來應用於資料串流編碼. 根據微軟提供的類別物件, 如下所示:
[ComVisibleAttribute(true)]
public sealed class DESCryptoServiceProvider : DES
可以看到這個是一個密封(sealed)的實體類別, 意味它不能被繼承. 不過他實作了DES類別. 來看看DES類別的架構:
[ComVisibleAttribute(true)]
public abstract class DES : SymmetricAlgorithm
這個DES是一個抽象, 不能被實例化. 由此推斷, 他的上層依然也是屬於抽象.
[ComVisibleAttribute(true)]
public abstract class SymmetricAlgorithm : IDisposable
這個上層的SymmetricAlgorithm對於DESCryptoProvider而言是最頂級的抽象, 這個頂級的抽象實作了IDisposable介面, 表示由下直接或間接繼承的具象類別所產生的實例物件可以被直接摧毀(Dispose). 在微軟的DES加密服務提供64位元長度.
根據DESCryptoServiceProvider的完整類別繼承結構如下所示:
System.Object
System.Security.Cryptography.SymmetricAlgorithm
System.Security.Cryptography.DES
System.Security.Cryptography.DESCryptoServiceProvider
這個服務類別來自mscorlib (在 mscorlib.dll 中), 因此在創建專案, 直接就可以使用了, 預設他會匯入mscorlib.dll的參考.
以下根據MSDN的基本範例來看看怎麼使用DES加密服務:
private static void EncryptData(String inName, String outName, byte[] desKey, byte[] desIV)
{
//Create the file streams to handle the input and output files.
FileStream fin = new FileStream(inName, FileMode.Open, FileAccess.Read);
FileStream fout = new FileStream(outName, FileMode.OpenOrCreate, FileAccess.Write);
fout.SetLength(0);
//Create variables to help with read and write.
byte[] bin = new byte[100]; //This is intermediate storage for the encryption.
long rdlen = 0; //This is the total number of bytes written.
long totlen = fin.Length; //This is the total length of the input file.
int len; //This is the number of bytes to be written at a time.
DES des = new DESCryptoServiceProvider();
CryptoStream encStream = new CryptoStream(fout, des.CreateEncryptor(desKey, desIV), CryptoStreamMode.Write);
Console.WriteLine("Encrypting...");
//Read from the input file, then encrypt and write to the output file.
while(rdlen < totlen)
{
len = fin.Read(bin, 0, 100);
encStream.Write(bin, 0, len);
rdlen = rdlen + len;
Console.WriteLine("{0} bytes processed", rdlen);
}
encStream.Close();
fout.Close();
fin.Close();
}
上述是一個DES加密服務的實體操作(EncryptData), 用來對資料串流進行加密, 這個操作傳入四個引數, 分別來說明這四個引數的作用:
1. inName: 這個參數是要傳入資料串流的實體路徑
2. outName: 將取得的加密資料串流寫回指定路徑
3. desKey: 這個是DES所使用的加密金鑰
4. desIV: 這是CBC的初始化向量, DES必須要用到, 他不是ECB
下面這兩句陳述式是重要的關鍵, 主要是依賴他們來提供DES私鑰加密服務. 就以OOD的角度來看, 也牽涉到非常實用的設計模式, 不管在JAVA或著.NET, I/O的包裝很常使用:
DES des = new DESCryptoServiceProvider();
CryptoStream encStream = new CryptoStream(fout, des.CreateEncryptor(desKey, desIV), CryptoStreamMode.Write);
第一句的des物件變數, 這是一個針對抽象實踐的方式, 通用的一點說法就是一般化. DESCryptoServiceProvider具象類別則是主要用來提供對DES相關簽名的實作. 在CryptoStream這段, 來看看他的建構函式, new CryptoStream(...), 他有一個非常重要的特殊性, 首先看他傳入的參數, 其中包含了fout的FileStream物件, CryptoStream具象類別和這個類別就某種程度來說有很大的相關性, 這種相關性先看他是否有實作抽象.
[ComVisibleAttribute(true)]
public class FileStream : Stream
[ComVisibleAttribute(true)]
public class CryptoStream : Stream, IDisposable
這上面的類別架構可以看到了這兩個具象類別結實做了Stream的抽象, 這是相當重要的存在性. 對Stream的抽象來說, FileStream或著CryptoStream都是他的實現, 換句話說, FileStream或著CryptoStream可以是Stream的一種:
Stream sUsingFs=new FileStream(...);
Stream sUsingCs=new CryptoStream(...);
不管FileStream或著CryptoStream都可以是Stream, 在物件導向的觀點來看, 就是對Stream的多型展現, 這種一般化的設計在OOA的設計上可以達到幾個存在性法則:
1. OCP(Open-Close Principle)
2. DIP(Dependency Inversion Principle)
3. LSP(Lisksov Principle)
這些法則在實務設計上廣泛且常應用, 透過這些法則可以達到好幾種模式的展現. 在這個Stream的多型展現上, 往回他的具象類別來看, CryptoStream的建構函數在前面有提到, 一個fout的FileStream物件被傳入, 如果仔細再看他的完整建構函式表示:
public CryptoStream (
Stream stream,
ICryptoTransform transform,
CryptoStreamMode mode
)
可以注意到, 他真正傳入的型別是Stream抽象, 就高層次的角度來看, CryptoStream只知道有一個Stream物件被傳入, 但他並不知道是FileStream的真正引入, 也就是說CryptoStream並不清楚真被傳入的Stream物件真正細節為何, 代表這是某種程度的封裝, 允許隱藏實際的細節. 非常有意義, 但是在這段建構函式真正的重點在於CryptoStream是Stream的一種; 而FileStream也是, 這兩者對Stream來說是IS-A的關係, 一個Stream的CryptoStream包入Stream的Filetream, 兩者細節可用.NET Reflector拆解, 這邊就不詳細了. Stream包Stream在有些教科書學術的說法稱為包裏效果, 但是在OOD的角度來看, 他是非常經典的Decorator Pattern實現, 這個模式是相當棒的, 而且實用價值極高:
動態地給一個物件附加一些額外的職責, 同時保持相同的介面. 就擴展功能來說, 裝飾模式(Decorator)提供了繼承機制的彈性替代方案.
可以從UML來觀察這個特點- 動態地給一個物件附加一些額外的職責:
為此, 舉一個簡單的例子來看. 有一個類別用於T_A1編碼, 另一個類別用於T_B1編碼, 假設有一個目標要從T_A1將一個二進位檔案重編碼, 然後轉到T_B1再次重編碼, 依照傳統的繼承設計, 可從T_B1繼承去重改寫, 這個問題便可解決, 但是如果要改寫20個呢? 首先你會遭遇到傳說中的類別爆炸, 對於設計來說這個現象的發生是相當瘋狂的, 因為它等於要從這20個類別去個別繼承重寫新類別, 完全吃裏不討好, 除了累死自己在維護困難度也大幅提高. 面對這種情況, 我們需要的是重構(Refactor)來表達對這個現象的反抗. 採用Decorator便是提供一種方法.
T_A1和T_B1都有一種共通的現象, 那就是它們主要用來做編碼-Encode, 行為都是一樣的. 因此可以將它進行一般化:
interface T_DEV
讓T_A1和T_B1都是這個抽象的實現, 因為T_A1和T_B1可能存在相異性, 最好的一種確保作法是往上在抽象一層:
public abstract class T_DEV_A: T_DEV
public abstract class T_DEV_B: T_DEV
在兩層的設計下依然是相當合理, 並且依然維持對抽象的實現, 那麼T_A1和T_B1理所當然的是實作個別的抽象:
public sealed class T_A1: T_DEV_A
public sealed class T_B1: T_DEV_B
透過sealed關鍵字封殺了對下層繼承的使用, 因為對具象實現的做法應當是盡量避免. 不管對T_A1或著T_B1來說, 都可使是T_DEV的一種, 並且在T_DEV來說, 設計了一個簽名- Encode:
T_DEV Encode(...)
可以看到這個簽名返回的依然是T_DEV, 這顯然是有用意的, 那麼依據上述的UML類別圖來對應:
1. T_DEV屬於IComponent
2. T_DEV_A和T_DEV_B皆屬於個別的Decorator
3. T_A1和T_B1皆屬於對應T_DEV_A和T_DEV_B的instance class
IComponent包含了Encod(...)的簽章讓向下的具象類別得以實現, 為了方便性, 可以再加入另一種簽章:
byte[] GetBytes()
這個簽章直接返回基底型別的byte陣列, 這種簽章的加入得以讓任何實踐的具象類別處理上更單純. 整體的代碼展現如下:
interface T_DEV{
T_DEV Encode(...);
byte[] GetBytes();
}
public abstract class T_DEV_A: T_DEV {
protected T_DEV _tDev;
public T_DEV_A(T_DEV tDev){
_tDev=tDev;
}
}
public abstract class T_DEV_B: T_DEV {
protected T_DEV _tDev;
public T_DEV_B(T_DEV tDev){
_tDev=tDev;
}
}
public sealed class T_SRC<T>: T_DEV{
//skip
T _obj
public T_SRC(T obj){
_obj=obj;
}
public override T_DEV Encode(...){
//skip
return this;
}
public override GetBytes(){
byte[] bContent;
//skip
return bContent;
}
}
public sealed class T_A1: T_DEV_A{
public T_A1(T_DEV tDev): base(tDev){
//skip
}
public override T_DEV Encode(...){
byte[] bContent=_tDev.GetBytes();
//skip
return this;
}
public override GetBytes(){
byte[] bContent;
//skip
return bContent;
}
}
public sealed class T_B1: T_DEV_B{
public T_B1(T_DEV tDev): base(tDev){
//skip
}
public override T_DEV Encode(...){
byte[] bContent=_tDev.GetBytes();
//skip
return this;
}
public override GetBytes(){
byte[] bContent;
//skip
return bContent;
}
}
將原本的代碼重新構造以後, 得到了上面這個泛用性架構, 對於T_B1而言, 建構函數傳入取得的引數型別為T_DEV的介面, 也就是說他並不清楚真正實例化的細節, 僅僅只清楚有一個實例化物件被傳入, 這是一種封裝. Encode(...)操作對傳入的_tDev進行加工, 使用GetBytes()取得最單純的byte陣列資料. 另外有一個特殊的T_DEV_SRC的具象類別, 這是一個本質類別, 並且使用的泛型設計, 傳入以T的泛型為基準的obj物件引數, 本質類別的作用在於將一個差異化的其他物件進行轉換為相容於T_DEV型別的物件以利於操作. 那麼, 整體上在某這場景的client, 可以如下所示:
string value="some content";
T_SRC<string> tSrc=new T_SRC<string>(value);
T_A1 tA1=new T_A1(tSrc);
T_B1 tB1=new T_B1(tA1);
byte[] bB1Content=tB1.GetBytes();
上述可以看到, 經過改寫過後, 這是一個相當彈性的擴充架構, 不會造成極大範圍的變化, 由於這樣的設計讓類別爆炸現象得以避免. Decorator Pattern這樣的設計便是取代繼承的替代性方案, 允許的是擴充, 盡量避免原有的修改, 這是OCP的符合.
首先, fin的FileStream物件會取得實體檔案的串流, 根據傳入的實體路徑參數而定. fout則是用來將取得的加密資料串流寫回指定路徑.
1. fin: 傳入三個引數, 分別是=> 實體路徑, 允許檔案模式為開啟和允許檔案存取為讀取
2. fout: 同樣也是三個引數=> 實體路徑, 允許檔案模式為打開或建立和允許檔案存取為寫入
fout.SetLength(0)預先設定他的串流大小為0, 也就是在這個狀況沒有任何資料. 另外說明幾個變數:
1. bin: 長度為100的bin位元組參數用還放置讀取的二進位資料串流, 一次讀取100長度
2. rdlen: 作為目前讀取的檔案長度, 表示其位址
3. totlen: 放置fin讀取目前資料串流的實際大小
4. len: 放置目前的讀取大小