什么是外观模式?
外观模式是一种结构型设计模式,它为复杂的子系统提供一个简单的接口。外观模式通过创建一个外观类来隐藏一个复杂系统的复杂性,使得客户端只需要与外观类交互,而不需要直接与复杂的子系统交互。
外观模式包含以下角色:
- 外观(Facade):知道哪些子系统类负责处理请求,将客户端的请求代理给适当的子系统对象
- 子系统(Subsystems):实现子系统的功能,处理由Facade对象指派的任务,没有Facade的任何相关信息
- 客户端(Client):通过Facade接口与子系统进行交互
外观模式的优缺点
优点:
- 简化客户端使用:客户端不需要了解子系统的复杂性,只需要与外观类交互
- 降低耦合度:客户端与子系统之间松耦合,子系统的变化不会影响客户端
- 提高灵活性:可以灵活地切换不同的子系统实现
- 更好的分层:有助于建立分层结构,每层之间通过外观接口交互
- 符合迪米特法则:减少了客户端与子系统之间的依赖关系
缺点:
- 可能违反开闭原则:当需要添加新功能时,可能需要修改外观类
- 可能创建上帝对象:外观类可能变得过于庞大和复杂
- 限制了底层功能:客户端无法直接访问子系统的高级功能
什么场景下使用外观模式
- 为复杂的模块或子系统提供简单的接口
- 客户端程序需要与多个子系统进行交互
- 需要将子系统与客户端解耦,提高子系统的独立性和可移植性
- 构建多层结构的系统,利用外观模式定义系统中每层的入口
- 需要为遗留代码提供现代接口
代码举例
这里以经常碰到的操作文件的业务场景为例
import java.io.*;
import java.nio.file.*;
import java.util.zip.*;
import java.security.*;
import java.nio.charset.StandardCharsets;
import java.util.*;// 子系统1 - 文件读写操作
class FileIOManager {public String readFile(String filePath) throws IOException {System.out.println("读取文件: " + filePath);return Files.readString(Paths.get(filePath), StandardCharsets.UTF_8);}public void writeFile(String filePath, String content) throws IOException {System.out.println("写入文件: " + filePath);Files.writeString(Paths.get(filePath), content, StandardCharsets.UTF_8);}public void appendToFile(String filePath, String content) throws IOException {System.out.println("追加到文件: " + filePath);Files.writeString(Paths.get(filePath), content, StandardCharsets.UTF_8, StandardOpenOption.CREATE, StandardOpenOption.APPEND);}public boolean fileExists(String filePath) {return Files.exists(Paths.get(filePath));}public void deleteFile(String filePath) throws IOException {System.out.println("删除文件: " + filePath);Files.delete(Paths.get(filePath));}public long getFileSize(String filePath) throws IOException {return Files.size(Paths.get(filePath));}
}// 子系统2 - 文件压缩解压缩
class CompressionManager {public void compressFile(String sourceFile, String zipFile) throws IOException {System.out.println("压缩文件: " + sourceFile + " -> " + zipFile);try (FileOutputStream fos = new FileOutputStream(zipFile);ZipOutputStream zos = new ZipOutputStream(fos)) {File fileToZip = new File(sourceFile);FileInputStream fis = new FileInputStream(fileToZip);ZipEntry zipEntry = new ZipEntry(fileToZip.getName());zos.putNextEntry(zipEntry);byte[] bytes = new byte[1024];int length;while ((length = fis.read(bytes)) >= 0) {zos.write(bytes, 0, length);}fis.close();}}public void decompressFile(String zipFile, String destDir) throws IOException {System.out.println("解压缩文件: " + zipFile + " -> " + destDir);byte[] buffer = new byte[1024];try (FileInputStream fis = new FileInputStream(zipFile);ZipInputStream zis = new ZipInputStream(fis)) {ZipEntry zipEntry = zis.getNextEntry();while (zipEntry != null) {File newFile = new File(destDir, zipEntry.getName());if (zipEntry.isDirectory()) {newFile.mkdirs();} else {new File(newFile.getParent()).mkdirs();FileOutputStream fos = new FileOutputStream(newFile);int len;while ((len = zis.read(buffer)) > 0) {fos.write(buffer, 0, len);}fos.close();}zipEntry = zis.getNextEntry();}zis.closeEntry();}}public List<String> listZipContents(String zipFile) throws IOException {List<String> entries = new ArrayList<>();try (FileInputStream fis = new FileInputStream(zipFile);ZipInputStream zis = new ZipInputStream(fis)) {ZipEntry zipEntry = zis.getNextEntry();while (zipEntry != null) {entries.add(zipEntry.getName());zipEntry = zis.getNextEntry();}}return entries;}
}// 子系统3 - 文件加密解密
class EncryptionManager {public void encryptFile(String inputFile, String outputFile, String password) throws Exception {System.out.println("加密文件: " + inputFile + " -> " + outputFile);// 简化的加密实现(实际应用中应使用更安全的加密算法)String content = Files.readString(Paths.get(inputFile));String encrypted = Base64.getEncoder().encodeToString((content + ":" + password).getBytes(StandardCharsets.UTF_8));Files.writeString(Paths.get(outputFile), encrypted);}public void decryptFile(String inputFile, String outputFile, String password) throws Exception {System.out.println("解密文件: " + inputFile + " -> " + outputFile);String encrypted = Files.readString(Paths.get(inputFile));String decoded = new String(Base64.getDecoder().decode(encrypted), StandardCharsets.UTF_8);String[] parts = decoded.split(":", 2);if (parts.length < 2 || !parts[1].equals(password)) {throw new SecurityException("密码错误或文件损坏");}Files.writeString(Paths.get(outputFile), parts[0]);}public String calculateMD5(String filePath) throws Exception {System.out.println("计算文件MD5: " + filePath);MessageDigest md = MessageDigest.getInstance("MD5");byte[] data = Files.readAllBytes(Paths.get(filePath));byte[] digest = md.digest(data);StringBuilder sb = new StringBuilder();for (byte b : digest) {sb.append(String.format("%02x", b));}return sb.toString();}
}// 子系统4 - 文件备份管理
class BackupManager {public void createBackup(String sourceFile, String backupDir) throws IOException {System.out.println("创建备份: " + sourceFile + " -> " + backupDir);Path sourcePath = Paths.get(sourceFile);String fileName = sourcePath.getFileName().toString();String timestamp = String.valueOf(System.currentTimeMillis());String backupFileName = fileName + ".backup." + timestamp;Path backupPath = Paths.get(backupDir, backupFileName);Files.copy(sourcePath, backupPath, StandardCopyOption.REPLACE_EXISTING);}public void restoreBackup(String backupFile, String restorePath) throws IOException {System.out.println("恢复备份: " + backupFile + " -> " + restorePath);Files.copy(Paths.get(backupFile), Paths.get(restorePath), StandardCopyOption.REPLACE_EXISTING);}public List<String> listBackups(String backupDir) throws IOException {List<String> backups = new ArrayList<>();try (DirectoryStream<Path> stream = Files.newDirectoryStream(Paths.get(backupDir))) {for (Path entry : stream) {if (entry.toString().contains(".backup.")) {backups.add(entry.toString());}}}return backups;}
}// 子系统5 - 文件格式转换
class FormatConverter {public void convertTextFile(String inputFile, String outputFile, String targetEncoding) throws IOException {System.out.println("转换文本文件编码: " + inputFile + " -> " + outputFile + " (" + targetEncoding + ")");// 读取原文件String content = Files.readString(Paths.get(inputFile), StandardCharsets.UTF_8);// 写入目标文件(简化处理,实际需要处理编码转换)Files.writeString(Paths.get(outputFile), content, StandardCharsets.UTF_8);}public void convertToUpperCase(String inputFile, String outputFile) throws IOException {System.out.println("转换为大写: " + inputFile + " -> " + outputFile);String content = Files.readString(Paths.get(inputFile), StandardCharsets.UTF_8);Files.writeString(Paths.get(outputFile), content.toUpperCase(), StandardCharsets.UTF_8);}public void removeBlankLines(String inputFile, String outputFile) throws IOException {System.out.println("移除空行: " + inputFile + " -> " + outputFile);List<String> lines = Files.readAllLines(Paths.get(inputFile), StandardCharsets.UTF_8);List<String> nonEmptyLines = new ArrayList<>();for (String line : lines) {if (!line.trim().isEmpty()) {nonEmptyLines.add(line);}}Files.write(Paths.get(outputFile), nonEmptyLines, StandardCharsets.UTF_8);}
}// 外观类 - 文件处理外观
class FileProcessingFacade {private FileIOManager fileIOManager;private CompressionManager compressionManager;private EncryptionManager encryptionManager;private BackupManager backupManager;private FormatConverter formatConverter;public FileProcessingFacade() {this.fileIOManager = new FileIOManager();this.compressionManager = new CompressionManager();this.encryptionManager = new EncryptionManager();this.backupManager = new BackupManager();this.formatConverter = new FormatConverter();}// 简化的安全文件保存public boolean saveSecureFile(String filePath, String content, String password) {try {System.out.println("=== 安全保存文件 ===");// 1. 创建备份(如果文件已存在)if (fileIOManager.fileExists(filePath)) {backupManager.createBackup(filePath, System.getProperty("java.io.tmpdir"));}// 2. 保存临时文件String tempFile = filePath + ".tmp";fileIOManager.writeFile(tempFile, content);// 3. 加密文件encryptionManager.encryptFile(tempFile, filePath, password);// 4. 删除临时文件fileIOManager.deleteFile(tempFile);System.out.println("文件安全保存成功: " + filePath);return true;} catch (Exception e) {System.err.println("安全保存文件失败: " + e.getMessage());return false;}}// 简化的安全文件读取public String readSecureFile(String filePath, String password) {try {System.out.println("=== 安全读取文件 ===");// 1. 解密到临时文件String tempFile = filePath + ".decrypted";encryptionManager.decryptFile(filePath, tempFile, password);// 2. 读取内容String content = fileIOManager.readFile(tempFile);// 3. 删除临时文件fileIOManager.deleteFile(tempFile);System.out.println("文件安全读取成功: " + filePath);return content;} catch (Exception e) {System.err.println("安全读取文件失败: " + e.getMessage());return null;}}// 简化的文件打包和压缩public boolean createSecureArchive(String[] files, String archiveName, String password) {try {System.out.println("=== 创建安全压缩包 ===");// 1. 创建临时目录String tempDir = System.getProperty("java.io.tmpdir") + "/archive_temp_" + System.currentTimeMillis();new File(tempDir).mkdirs();// 2. 复制文件到临时目录for (String file : files) {Path source = Paths.get(file);Path target = Paths.get(tempDir, source.getFileName().toString());Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);}// 3. 压缩临时目录String zipFile = archiveName + ".zip";// 这里简化处理,实际应该压缩整个目录compressionManager.compressFile(files[0], zipFile);// 4. 加密压缩文件if (password != null && !password.isEmpty()) {String encryptedZip = archiveName + ".encrypted.zip";encryptionManager.encryptFile(zipFile, encryptedZip, password);fileIOManager.deleteFile(zipFile);}// 5. 清理临时目录deleteDirectory(new File(tempDir));System.out.println("安全压缩包创建成功: " + archiveName);return true;} catch (Exception e) {System.err.println("创建安全压缩包失败: " + e.getMessage());return false;}}// 简化的文件处理流水线public boolean processTextFile(String inputFile, String outputFile, ProcessingOptions options) {try {System.out.println("=== 处理文本文件 ===");String currentFile = inputFile;String tempFile = inputFile + ".temp";// 1. 创建备份if (options.createBackup) {backupManager.createBackup(inputFile, System.getProperty("java.io.tmpdir"));}// 2. 格式转换if (options.convertToUpperCase) {formatConverter.convertToUpperCase(currentFile, tempFile);fileIOManager.deleteFile(currentFile);currentFile = tempFile;tempFile = tempFile + ".temp2";}// 3. 移除空行if (options.removeBlankLines) {formatConverter.removeBlankLines(currentFile, tempFile);fileIOManager.deleteFile(currentFile);currentFile = tempFile;tempFile = tempFile + ".temp3";}// 4. 压缩(如果需要)if (options.compress) {String compressedFile = outputFile + ".zip";compressionManager.compressFile(currentFile, compressedFile);fileIOManager.deleteFile(currentFile);currentFile = compressedFile;} else {// 重命名为目标文件Files.move(Paths.get(currentFile), Paths.get(outputFile), StandardCopyOption.REPLACE_EXISTING);}System.out.println("文本文件处理成功");return true;} catch (Exception e) {System.err.println("处理文本文件失败: " + e.getMessage());return false;}}// 获取文件信息public FileInfo getFileInfo(String filePath) {try {FileInfo info = new FileInfo();info.setPath(filePath);info.setSize(fileIOManager.getFileSize(filePath));info.setMd5(encryptionManager.calculateMD5(filePath));info.setExists(fileIOManager.fileExists(filePath));return info;} catch (Exception e) {System.err.println("获取文件信息失败: " + e.getMessage());return null;}}// 辅助方法:删除目录private void deleteDirectory(File directory) {if (directory.exists()) {File[] files = directory.listFiles();if (files != null) {for (File file : files) {if (file.isDirectory()) {deleteDirectory(file);} else {file.delete();}}}directory.delete();}}
}// 配置类
class ProcessingOptions {public boolean createBackup = false;public boolean convertToUpperCase = false;public boolean removeBlankLines = false;public boolean compress = false;public ProcessingOptions setCreateBackup(boolean createBackup) {this.createBackup = createBackup;return this;}public ProcessingOptions setConvertToUpperCase(boolean convertToUpperCase) {this.convertToUpperCase = convertToUpperCase;return this;}public ProcessingOptions setRemoveBlankLines(boolean removeBlankLines) {this.removeBlankLines = removeBlankLines;return this;}public ProcessingOptions setCompress(boolean compress) {this.compress = compress;return this;}
}// 文件信息类
class FileInfo {private String path;private long size;private String md5;private boolean exists;// getters and setterspublic String getPath() { return path; }public void setPath(String path) { this.path = path; }public long getSize() { return size; }public void setSize(long size) { this.size = size; }public String getMd5() { return md5; }public void setMd5(String md5) { this.md5 = md5; }public boolean isExists() { return exists; }public void setExists(boolean exists) { this.exists = exists; }@Overridepublic String toString() {return "FileInfo{" +"path='" + path + '\'' +", size=" + size +", md5='" + md5 + '\'' +", exists=" + exists +'}';}
}// 客户端使用示例
public class FileProcessingDemo {public static void main(String[] args) {FileProcessingFacade fileProcessor = new FileProcessingFacade();try {// 创建测试文件String testFile = "test.txt";String content = "Hello World!\n\nThis is a test file.\n\nWith some blank lines.\n";Files.writeString(Paths.get(testFile), content);System.out.println("=== 安全文件操作演示 ===");// 安全保存文件boolean saveResult = fileProcessor.saveSecureFile(testFile, content, "mypassword");System.out.println("安全保存结果: " + saveResult);// 安全读取文件String readContent = fileProcessor.readSecureFile(testFile, "mypassword");System.out.println("读取内容: " + readContent);System.out.println("\n=== 文件处理流水线演示 ===");// 文件处理流水线ProcessingOptions options = new ProcessingOptions().setCreateBackup(true).setConvertToUpperCase(true).setRemoveBlankLines(true);boolean processResult = fileProcessor.processTextFile(testFile, "processed.txt", options);System.out.println("处理结果: " + processResult);System.out.println("\n=== 文件信息查询演示 ===");// 获取文件信息FileInfo fileInfo = fileProcessor.getFileInfo(testFile);System.out.println("文件信息: " + fileInfo);System.out.println("\n=== 压缩包创建演示 ===");// 创建压缩包String[] filesToArchive = {testFile, "processed.txt"};boolean archiveResult = fileProcessor.createSecureArchive(filesToArchive, "myarchive", "archivepass");System.out.println("压缩包创建结果: " + archiveResult);} catch (Exception e) {e.printStackTrace();}}
}