结构
策略模式(政策模式)定义一系列的算法,通过实现接口将其一个个封装起来,使它们可以相互替换。
策略模式包含以下3种角色:
- 策略(Strategy):
是一个接口类,用来定义与问题相关的若干个抽象方法(也可以使用抽象类)。 - 上下文(Context):
依赖于策略接口类的类(面向策略设计的类)。即该类中包含策略声明的变量(上转型),同时该类提供方法委托策略变量调用具体策略类所实现的接口的方法。 - 具体策略(ConcreteStrategy):
实现策略接口的类,即实现策略接口中所定义的抽象方法,给出具体算法。
策略模式中的角色UML类图如下图:
优点
- 上下文(Context) 和具体策略(ConcreteStrategy) 类是松耦合关系,即上下文类只要知道它要使用某一个实现策略(Strategy) 接口类的实例,但并不需要知道具体是哪一个类(使用上转型,Strategy strategy = new ConcreteStrategy())。
- 满足 “开-闭”原则(让设计对扩展开放,对修改关闭)。 当增加新的 具体策略类 时,不需要修改 上下文类 的代码,上下文就可以引用新的 具体策略类 的实例。
注:关于开闭原则,将设计分为核心部分和需求部分,核心部分是整个设计的基本结构,不是用来应对需求变化的,因此不能因为用户的需求变化而变化,此部分对修改关闭。当有需求变更和追加时,只允许在需求部分进行追加,而不需要也不允许修改现有的模块,即对扩展开放。
适用场所
- 一个类定义了多种行为,且这些行为在这个类的方法中以多个条件语句的形式出现,那么使用策略模式可以避免在类中使用大量的条件语句。
- 主要类(即上下文类 )不希望暴露复杂的、与算法相关的数据结构,那么可以使用策略模式封装算法,即将算法分别封装到具体策略 中。
与继承机制的对比
众所周知,面向对象型语言的一大特性就是继承。
通过继承可以改进对象的行为,子类可以重写父类的方法来改变该方法的行为,使得子类的对象具有和父类对象不同的行为。
但是,如果按照继承机制的用法,将父类的某个方法的内容的不同变体交给对应的子类去实现,就会使得这些实现和父类中的其他代码是高耦合关系,即父类的任何改动都会影响到所有的子类。 考虑到系统扩展性和复用性,应当注意面向对象的一个基本原则:少用继承,多用组合。
策略模式采用组合结构,将上下文类 的某个方法的内容的不同变体分别封装在不同的具体策略 中,而该上下文类 仅仅依赖于这些具体策略所实现的一个共同接口——策略类 。
实例
本问题中,需要根据设置的密码对文件进行加密和解密。
加密算法有两种,一是根据密码的字节数组对文件中的每一个字节进行加法运算,对应的解密算法为对密文做减法运算。二是根据密码的字节数组对文件中的每一个字节进行异或运算,对应的解密算法为对密文继续进行异或计算。
策略(Strategy):
分析本问题,已知所有算法都有加密和解密两个方法,加上设置密码,则抽象出的策略接口(Strategy) 如下,命名为EncryptStrategy。
EncryptStrategy.java
public interface EncryptStrategy{
public abstract void encryptFile(File file);
public abstract String decryptFile(File file);
public abstract void setPassword(String password);
}
具体策略(ConcreteStrategy):
对于本问题,有两个具体策略:StrategyOne 和 StrategyTwo ,分开讨论。
(1)StrategyOne类使用的加密算法
使用一个字符串做密码,如password,将password编码到一个字节数组:
...
byte[] p = password.getBytes();
...
假设p的长度为n,那么以n个字节为一组读取文件,对每一组中的字节,用数组p的对应字节做加法运算。比如,某一组中的n个字节为a[0]~a[n-1],那么对该字节的加密过程为:
...
for(i=0; i<n; i++){
a[i] = (byte)(a[i]+p[i]);
}
...
上述加密算法的解密算法是对密文做减法运算。
StrategyOne类全部代码如下:
StrategyOne.java:
public class StrategyOne implements EncryptStrategy{
String password;
public void setPassword(String password){
this.password = password;
}
public void encryptFile(File file){
try{
File tempFile = new File(".", "temp.txt");
byte[] secret = password.getBytes();
FileInputStream inputStream = new FileInputStream(file);
FileOutputStream outputStream = new FileOutputStream(tempFile);
int n = secret.length;
int m = -1;
byte[] content = new byte[n];
while ((m = inputStream.read(content, 0, n))!= -1){
for (int k=0; k<m; k++){
content[k] = (byte) (content[k] + secret[k]); //加密
}
outputStream.write(content, 0, m); //将加密文件写入临时文件中
}
inputStream.close();
outputStream.close();
inputStream = new FileInputStream(tempFile);
outputStream = new FileOutputStream(file);
byte[] c = new byte[1024];
while ((m = inputStream.read(c, 0, 1024))!=-1){
outputStream.write(c, 0, m); //用加密文件替换原来的文件
}
inputStream.close();
outputStream.close();
tempFile.delete();
} catch (IOException e){
System.out.println(e.toString());
}
}
public String decryptFile(File file){
try{
byte[] a = password.getBytes();
long length = file.length();
FileInputStream inputStream = new FileInputStream(file);
byte[] c = new byte[(int)length];
int n = inputStream.read(c);
for (int i = 0; i < n; i++) {
c[i] = (byte)(c[i] - a[i%a.length]); //解密
}
inputStream.close();
return new String(c);
} catch (IOException e){
return e.toString();
}
}
}
(2)StrategyTwo类使用的加密算法
其余步骤同上,只是将加法运算换成异或运算。比如,某一组中的n个字节为a[0]~a[n-1],那么对该字节的加密过程为:
...
for(i=0; i<n; i++){
a[i] = (byte)(a[i]^p[i]);
}
...
上述加密算法的解密算法是对密文继续做异或运算。
StrategyTwo类全部代码如下:
StrategyTwo.java:
public class StrategyTwo implements EncryptStrategy{
String password;
public void setPassword(String password){
this.password = password;
}
public void encryptFile(File file){
try{
File tempFile = new File(".", "temp.txt");
byte[] secret = password.getBytes();
FileInputStream inputStream = new FileInputStream(file);
FileOutputStream outputStream = new FileOutputStream(tempFile);
int n = secret.length;
int m = -1;
byte[] content = new byte[n];
while ((m = inputStream.read(content, 0, n))!= -1){
for (int k=0; k<m; k++){
content[k] = (byte) (content[k] ^ secret[k]); //加密
}
outputStream.write(content, 0, m); //将加密文件写入临时文件中
}
inputStream.close();
outputStream.close();
inputStream = new FileInputStream(tempFile);
outputStream = new FileOutputStream(file);
byte[] c = new byte[1024];
while ((m = inputStream.read(c, 0, 1024))!=-1){
outputStream.write(c, 0, m); //用加密文件替换原来的文件
}
inputStream.close();
outputStream.close();
tempFile.delete();
} catch (IOException e){
System.out.println(e.toString());
}
}
public String decryptFile(File file){
try{
byte[] a = password.getBytes();
long length = file.length();
FileInputStream inputStream = new FileInputStream(file);
byte[] c = new byte[(int)length];
int n = inputStream.read(c);
for (int i = 0; i < n; i++) {
c[i] = (byte)(c[i] ^ a[i%a.length]); //解密
}
inputStream.close();
return new String(c);
} catch (IOException e){
return e.toString();
}
}
}
上下文(Context):
上下文类命名为EncodeContext,该类包含策略声明的用于保存具体策略的引用的变量。代码如下:
EncodeContext.java:
public class EncodeContext{
EncryptStrategy strategy;
public void setStrategy(EncryptStrategy strategy){
this.strategy = strategy;
}
public void encryptFile(File file){
if(strategy!=null){
strategy.encryptFIle(file);
}else{
System.out.println("没有加密策略可用");
}
}
public String decryptFile(File file){
if(strategy!=null){
strategy.decryptFIle(file);
}else{
return "";
}
}
}
Application.java:
public class Application {
public static void main(String[] args){
File fileOne = new File("src/file/a.txt");
File fileTwo = new File("src/file/b.txt");
EncodeContext context = new EncodeContext();
EncryptStrategy one = new StrategyOne();
EncryptStrategy two = new StrategyTwo();
String password = "hello123456";
one.setPassword(password);
context.setStrategy(one);
context.encryptFile(fileOne);
System.out.println(fileOne.getName()+"加密后的内容:");
String s = "";
try{
FileReader reader = new FileReader(fileOne);
BufferedReader bufferedReader = new BufferedReader(reader);
while((s = bufferedReader.readLine())!=null){
System.out.println(s);
}
bufferedReader.close();
reader.close();
}catch (IOException e){
e.toString();
}
String str = context.decryptFile(fileOne);
System.out.println(fileOne.getName()+"解密后的内容为:");
System.out.println(str);
context.setStrategy(two);
password = "areyouok";
two.setPassword(password);
context.encryptFile(fileTwo);
System.out.println(fileTwo.getName()+"加密后的内容:");
try{
FileReader reader = new FileReader(fileTwo);
BufferedReader bufferedReader = new BufferedReader(reader);
while((s = bufferedReader.readLine())!=null){
System.out.println(s);
}
bufferedReader.close();
reader.close();
}catch (IOException e){
e.toString();
}
str = context.decryptFile(fileTwo);
System.out.println(fileTwo.getName()+"解密后的内容为:");
System.out.println(str);
}
}