Код логического суждения if-else с более чем 3 уровнями может быть реализован с использованием защитных операторов, шаблонов стратегии, шаблонов состояний и т. д. —— Руководство по разработке Java для Alibaba@[toc]
нужно:
Функция загрузки файлов является часто используемой системной функцией. Так где же сохраняются загруженные файлы? Разные компании могут планировать загрузку файлов на разные серверы в зависимости от разных факторов, таких как: локальный сервер, передача на ftp-сервер, сервер FastDFS, сервер hdfs и т. д.
if else
В общем случае мы используем условное суждение, если еще
/**
* if else 版本的上传文件代码
*/
public class FileClient{
private final static String LOCAL = "local";
private final static String FTP = "ftp";
private final static String FASTDFS = "fastdfs";
private final static String HDFS = "hdfs";
/**
* 上传文件
*
* @param storageType 文件存储方式
* @param file 文件
*/
public void uploadFile(String storageType, String file) {
if (storageType.equals(LOCAL)) {
System.out.println("文件" + file + "已上传到 本地服务器");
} else if (storageType.equals(FTP)) {
System.out.println("文件" + file + "已上传到 ftp服务器");
} else if (storageType.equals(FASTDFS)) {
System.out.println("文件" + file + "已上传到 fastdfs服务器");
} else if (storageType.equals(HDFS)) {
System.out.println("文件" + file + "已上传到 hdfs服务器");
} else {
System.out.println("输入的文件存储类型错误");
}
}
public static void main(String[] args) {
FileClient fileClient = new FileClient();
fileClient.uploadFile("hdfs","ifelse.txt");
}
}
заявление охраны
Большое количество операторов if else в коде вызывает у людей головокружение, и при чтении кода легко пройтись по всему коду естественным образом. Отсюда и оператор Guard, который используется для упрощения обработки, необходимой для просмотра кода.
/**
* 上传文件
*
* @param storageType 文件存储方式
* @param file 文件
*/
public void uploadFileWithoutElse(String storageType, String file) {
if (storageType.equals(LOCAL)) {
System.out.println("文件" + file + "已上传到 本地服务器");
return;
}
if (storageType.equals(FTP)) {
System.out.println("文件" + file + "已上传到 ftp服务器");
return;
}
if (storageType.equals(FASTDFS)) {
System.out.println("文件" + file + "已上传到 fastdfs服务器");
return;
}
if (storageType.equals(HDFS)) {
System.out.println("文件" + file + "已上传到 hdfs服务器");
return;
}
System.out.println("输入的文件存储类型错误");
}
После удаления else код также становится более похожим на абзацы и выглядит более удобным.
Изменение требования: добавить путь ко мне, загрузить в облако Qiniu
Код здесь очень простой, нам нужно только добавить условие if или if else, но код всего файла FileUtils будет изменен, поэтому на этот раз появится новый метод.
Давайте поговорим об использовании шаблонов проектирования для уменьшения количества ситуаций if else:
Простой заводской шаблон
Простой фабричный шаблон относится к порождающему шаблону, также известному как статический фабричный метод, который относится к порождающему шаблону класса.В простом фабричном шаблоне экземпляры разных классов могут быть возвращены в зависимости от параметров.Простой фабричный шаблон специально определяет класс, ответственный за создание экземпляров других классов, и созданные экземпляры обычно имеют общий родительский класс.
Здесь мы абстрагируем поведение загрузки каждого метода хранения:
- Аннотация
/**
* 抽出各个方式共同的行为接口:上传文件
*/
public interface IStorageType {
void uploadFile(String file);
}
- Простая фабрика (статическая фабрика)
/**
* 简单工厂
*/
public class StorageTypeFactory {
private final static String LOCAL = "local";
private final static String FTP = "ftp";
private final static String FASTDFS = "fastdfs";
private final static String HDFS = "hdfs";
public static IStorageType storageTypeCreate(String storageType) {
IStorageType iStorageType = null;
switch (storageType) {
case LOCAL:
iStorageType = new LocalStorageType();
break;
case FTP:
iStorageType = new FtpStorageType();
break;
case FASTDFS:
iStorageType = new FastDfsStorageType();
break;
case HDFS:
iStorageType = new HdfsStorageType();
break;
}
return iStorageType;
}
}
- Конкретный метод загрузки (в качестве примера возьмем hdfs, другие аналогичны)
public class HdfsStorageType implements IStorageType {
@Override
public void uploadFile(String file) {
System.out.println("文件" + file + "已上传到 hdfs服务器");
}
}
- Звонок клиента
/**
* 简单工厂模式 版本的上传文件代码
*/
public class FileClient {
public static void main(String[] args) {
IStorageType iStorageType = StorageTypeFactory.storageTypeCreate("hdfs");
iStorageType.uploadFile("simpleFactory.txt");
}
}
Вернемся к сцене: в настоящее время, когда мы хотим добавить метод облачного хранилища Qiniu, мы можем напрямую добавить условие переключения на заводе и добавить определенное поведение облачного хранилища Qiniu.
- преимущество:
- Клиент может изменить параметры без изменения других кодов.Клиент не несет ответственности за создание конкретного метода хранения, снижение зависимости от конкретного метода хранения, а оставляет это дело на усмотрение завода. Воплощает принцип единой ответственности.
- При добавлении других методов или сокращении других методов вы можете напрямую добавлять классы и переключать условия, не затрагивая конкретный логический код, и модификация относительно проста.
- недостаток (хотя это и нарушаетпринцип открыто-закрыто, потому что, когда он открыт для расширения, он также открыт для модификации. Я лично считаю, что количество кода, связанного с ним, не особенно велико, и я иногда рассматриваю возможность изменить код таким образом)
Сценарии, в которых подходит простой шаблон factory: 1. Веток суждения не много, или логика суждения проста 2. Клиенту нужно запомнить только параметры, а не конкретные детали реализации
Справочная документация:
Фабричный шаблон -- Простой заводской шаблон
режим стратегии
Объяснение шаблона стратегии в «Шаблонах проектирования Dahua» следующее: он определяет и инкапсулирует алгоритмы, так что алгоритмы могут быть заменены друг другом, так что изменения алгоритма не повлияют на пользователей, которые используют алгоритм. . Это предложение немного сложно понять. Давайте реализуем код в соответствии с шаблоном стратегии:
- Стратегия
public abstract class StorageStrategy{
public abstract void uploadFile(String file);
}
- конкретные стратегии
public class FtpStorageStrategy extends StorageStrategy {
@Override
public void uploadFile(String file) {
System.out.println("文件" + file + "已上传到 ftp服务器");
}
}
- политический контекст
/**
* 策略模式的上下文。
* 这里策略模式让我觉得有些类似像代理模式,于是去查了一下。
* 简单代理模式与策略模式在功能上的很大的区别是:
*
* 简单代理模式中,代理类知道被代理类的行为,因为代理类与被代理类实现的是同一个接口,因此代理类与被代理类的结构是相同的;
*
* 而策略模式中,策略容器并不知道内部策略的详细信息,因为容器并没有实现与内部策略相同的接口,
* 即容器与内部策略只是简单的组合关系,容器只是将内部策略的行为抽取出来,进行了统一的实现。
*
*/
public class StorageContext {
private StorageStrategy storageStrategy;
public StorageContext(StorageStrategy storageStrategy) {
this.storageStrategy = storageStrategy;
}
public void uploadFileAction(String file){
storageStrategy.uploadFile(file);
}
}
Мысль 1: разница между режимом агента и режимом стратегии:
Контекст очень похож на режим прокси, фактически разница между режимом прокси и режимом стратегии заключается в следующем:
- В простом прокси-режиме прокси-класс знает поведение прокси-класса, потому чтоПрокси-класс и прокси-класс реализуют один и тот же интерфейс, поэтому структура прокси-класса и прокси-класса одинакова.;
- В режиме политики контейнер политики не знает деталей внутренней политики, поскольку контейнер не реализует тот же интерфейс, что и внутренняя политика. То есть контейнер и внутренняя стратегия — это просто простая комбинация, а контейнер только извлекает поведение внутренней стратегии и реализует его единообразно.
- Звонок клиента
/**
* 策略模式 版本的上传文件代码
*/
public class FileClient {
private final static String LOCAL = "local";
private final static String FTP = "ftp";
private final static String FASTDFS = "fastdfs";
private final static String HDFS = "hdfs";
public static void main(String[] args) {
StorageContext storageContext = null;
//模拟入参
String storageType = "ftp";
switch (storageType) {
case LOCAL:
//客户端需要知道具体有哪些策略,能做什么。但是不需要知道策略具体怎么做
storageContext = new StorageContext(new LocalStorageStrategy());
break;
case FTP:
storageContext = new StorageContext(new FtpStorageStrategy());
break;
case FASTDFS:
storageContext = new StorageContext(new FastDfsStorageStrategy());
break;
case HDFS:
storageContext = new StorageContext(new HdfsStorageStrategy());
break;
}
storageContext.uploadFileAction("strategy.txt");
}
}
Мысли два:
Здесь логика if else фактически передается вызывающей стороне (клиенту) для обработки (используется переключатель), что очень сбивает с толку. Я видел некоторые в Интернете, которые используют Map для предварительного хранения отношения отображения между условиями суждения и стратегиями.Я думаю, что это временное решение, потому что этот метод может напрямую заменить самый примитивный if else с помощью карты. Прочитав следующую статью, я до сих пор не соприкасаюсь со своим замешательством.Спасите заполненный экран если-иначе в режиме стратегии
Лично считаю, что если if else изменено, входные параметры не должны изменяться, то есть (String storageType, String file). Эти два используются в качестве входных параметров для клиента.
Существующие решения в Интернете:
- использовать перечисление
- использовать переключатель
- использовать карту Однако каждый раз, когда вы добавляете стратегию, вам нужно добавлять условия к описанному выше методу.Тогда я могу каждый раз добавлять соответствующую стратегию и использовать новую стратегию напрямую, не добавляя условий оценки?На самом деле это возможно.Давайте посмотрим, как это работает в сочетании с нашей часто используемой средой Spring.
Как с этим справляется Spring
Сначала введите зависимости maven, здесь напрямую используйте зависимости springboot-web (удобнее)
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
Тогда наш интерфейс стратегии должен наследовать интерфейс InitializingBean Spring.
public interface IStorageType extends InitializingBean {
void uploadFile(String file);
}
Затем мы используем шаблон singleton для поддержания соответствия между строками и стратегиями хранения:
/**
* 使用了饿汉式的单例模式
*/
public class StorageMapSingleton {
private HashMap<String, IStorageType> map = new HashMap<>();
private static StorageMapSingleton storageMapSingleton = new StorageMapSingleton();
private StorageMapSingleton(){
}
public static StorageMapSingleton getInstance(){
return storageMapSingleton;
}
public IStorageType getStorageType(String type){
return map.get(type);
}
/**
* 这里使用默认的访问权限,允许在同一个package下面调用该方法。当前类必须和具体的实现类在同一个package下
* @param type
* @param storageType
*/
void putStorageType(String type, IStorageType storageType){
map.put(type, storageType);
}
}
Далее следует реализовать конкретную стратегию регистрации и поддерживать отношения сопоставления.
@Component
public class FastDfsStorageType implements IStorageType {
@Autowired
private FastDfsStorageType fastDfsStorageType;
private final static String LOCAL = "local";
@Override
public void uploadFile(String file) {
System.out.println("文件" + file + "已上传到 fastdfs服务器");
}
/**
* 当前 bean 被实例化后,会执行下面方法把字符串和策略的对应关系传进去
*/
@Override
public void afterPropertiesSet() {
StorageMapSingleton.getInstance().putStorageType(LOCAL, fastDfsStorageType);
}
}
Давайте создадим тестовый класс, чтобы увидеть, правильно ли он работает:
@RunWith(SpringRunner.class)
@SpringBootTest(classes = StorageApplication.class)
public class StorageTypeTest {
@Before
public void testBefore(){
System.out.println("测试前");
}
@After
public void testAfter(){
System.out.println("测试后");
}
@Test
public void storageTest(){
IStorageType iStorageType = StorageMapSingleton.getInstance().getStorageType("hdfs");
iStorageType.uploadFile("策略模式.txt");
}
}
Консоль может нормально выводить:文件策略模式.txt已上传到 本地服务器
Когда мы добавляем класс стратегии: метод облачного хранилища Qiniu
@Component
public class QiniuStorageType implements IStorageType {
@Autowired
private QiniuStorageType qiniuStorageType;
private final static String QINIU = "qiniu";
@Override
public void uploadFile(String file) {
System.out.println("文件" + file + "已上传到七牛云");
}
@Override
public void afterPropertiesSet() throws Exception {
StorageMapSingleton.getInstance().putStorageType(QINIU, qiniuStorageType);
}
}
Затем выполните тестовый метод:
@Test
public void storageTest(){
IStorageType iStorageType = StorageMapSingleton.getInstance().getStorageType("qiniu");
iStorageType.uploadFile("策略模式.txt");
}
вывод:文件策略模式.txt已上传到七牛云
Этого можно достичь путем добавления только одного класса и использования новой стратегии без изменения других файлов.
Резюме: В настоящее время отношение сопоставления Map фактически используется для поддержания отношения политики, но благодаря одноэлементному режиму и интерфейсу Spring InitializingBean мы избегаем изменения других классов. Хотя код немного сложнее, когда мы вносим изменения в стратегию, мы уменьшаем количество изменений и нам не нужно менять условие if else. PS: На самом деле, если вы не полагаетесь на Spring InitializingBean, вы также можете добиться эффекта сохранения сопоставления карт с помощью отражения java.