В некоторых старых проектах мы должны часто сталкиваться со следующими сценариями (если мы не можем столкнуться с ними, нам не нужно продолжать чтение): Некоторые тела методов некоторых классов имеют логику для оценки одной и той же переменной. Если в теле этих методов не так много методов, которые нужно обнулить, хорошо сказать, что если большинство методов нужно обнулить для переменной, то эти коды часто сложно поддерживать.
Например:
Этот метод суждения о большом количестве одной и той же переменной принесет следующие недостатки (тщательный вкус недостатков является важной основой для понимания последующих улучшений):
- Повторение этой логики оценки null в нескольких местах приведет к созданию большого количества повторяющегося кода, который нелегко поддерживать.
- Если большая часть кода в классе относится к этому типу кода, коллеги обычно тратят больше времени на его понимание и дольше думают над его расширением.
- Эта нулевая логика не может защитить недавно введенные новые методы от нулевых.Если новый метод написан, но нулевая логика забыта, могут возникнуть нулевые ошибки.
Рассмотрим конкретный пример: в проектах электронной коммерции у нас всегда есть единый вход и выход для обработки платежей, существует много видов платежного поведения, например, распространенный — использовать купоны и не использовать купоны.
public class PayProcess {
private CouponInfo couponInfo;
public void setCouponInfo(CouponInfo couponInfo) {
this.couponInfo = couponInfo;
}
//检查支付的合法性
public boolean checkLegitimate() {
if (null != couponInfo) {
//其实这里主要就是检查一下券有没有过期
return couponInfo.checkLeg();
}
//如果没有券 就意味着合法
return true;
}
//获取实际支付金额
public int getPayValue(int totalValue) {
if (null != couponInfo) {
//支付总金额 减去 券的金额 自然就是需要支付的金额
return totalValue - couponInfo.getCouponValue();
}
return totalValue;
}
//获取优惠券的类型
public int getCouponType() {
if (null != couponInfo) {
return couponInfo.getCouponType();
}
//如果压根就没有优惠券 这里就返回0 0代表没有优惠券, 实际业务中 我们不能写这种魔法数字
//一定要定义成常量,这里为了演示方便 我就偷懒了
return 0;
}
}
class CouponInfo {
public boolean checkLeg() {
//实际中 我们会校验券的时间 等等,现在为了演示方便 我就直接返回一个false了
//大家知道意思就好
return false;
}
public int getCouponValue() {
//返回券的实际价值,这里也是为了演示方便 我直接返回一个固定值
return 3;
}
public int getCouponType() {
//返回券的种类,看看是无敌券?还是限定品类的券 等等
//为了演示方便 我直接写一个int值,实际写的时候 一定要写成常量
return 2;
}
}
Когда мы используем эту платежную систему, определенно будет несколько сценариев использования, в некоторых сценариях используются купоны, а в некоторых нет. Например:
public static void main(String[] args) {
//这里是用券的
PayProcess p1 = new PayProcess();
p1.setCouponInfo(new CouponInfo());
//这里是没有用券的
PayProcess p2 = new PayProcess();
p1.setCouponInfo(null);
}
Невооруженным глазом видно, что нашему PayProcess приходится писать много пустого кода. Защитное программирование никогда не помешает. Это также важный момент, упомянутый в Руководстве по разработке Java для Ali.Если массив должен быть оценен как пустой, он должен быть оценен как пустой, и если массив должен быть оценен как выходящий за пределы, массив должен быть вне границ. границы. Идея правильная, но такой код легко может попасть в ад пустых суждений. О каких недостатках говорилось в начале нашей статьи.
Как рефакторить эту часть старого кода? Сделать его таким плохим?
Добавляем новый класс (на самом деле основная цель здесь — единообразно обрабатывать нулевые случаи):
//这里面的逻辑 注意看 其实和PayProcess 里面当券为null的时候逻辑一样的
public class NullCouponInfo extends CouponInfo {
public boolean checkLeg() {
return true;
}
//没有券 那券的价值就为0
public int getCouponValue() {
return 0;
}
// 没有券 自然type为0
public int getCouponType() {
return 0;
}
}
Тогда наш класс оплаты можно значительно обновить:
public class PayProcess {
private CouponInfo couponInfo;
public void setCouponInfo(CouponInfo couponInfo) {
this.couponInfo = couponInfo;
}
//检查支付的合法性
public boolean checkLegitimate() {
//其实这里主要就是检查一下券有没有过期
return couponInfo.checkLeg();
}
//获取实际支付金额
public int getPayValue(int totalValue) {
//支付总金额 减去 券的金额 自然就是需要支付的金额
return totalValue - couponInfo.getCouponValue();
}
//获取优惠券的类型
public int getCouponType() {
return couponInfo.getCouponType();
}
}
При последнем звонке, когда купон пуст, не передавать null в качестве параметра
PayProcess p3 = new PayProcess();
p1.setCouponInfo(new NullCouponInfo());
Вы можете видеть, что после этого изменения вся логика стала намного понятнее, а читабельность тоже очень хорошая. Также не так много дублированного кода. Конечно, здесь есть скрытая опасность: Когда нам нужно добавить какие-то новые методы в купон, нам нужно изменить NullCouponInfo в дополнение к изменению CouponInfo.Если мы изменим его, это оставит скрытые опасности в классе PayProcess. Хотя не будет исключения нулевого указателя, но часто мы не получим желаемых результатов.
Фактически, для этого сценария нам нужно только абстрагировать интерфейс. Пусть наши CouponInfo и NullCouponInfo наследуют интерфейс: IКупон (Не делайте CouponInfo родительским классом NullCouponInfo.), так что все новые методы нужно добавлять только в интерфейс, чтобы при компиляции нам предлагалось реализовать в обоих подклассах. Чтобы избежать вышеупомянутых скрытых опасностей. (Код здесь относительно прост и не будет демонстрироваться)
Суммировать:Заменить всю нулевую логику нулевым объектом, вы сможете решить задачу, поставленную в начале нашей статьи. Вот некоторые ключевые моменты:
- Если бизнес-логика проста, введение режима нулевых объектов вместо этого увеличит объем кода. Поэтому пользователям необходимо иметь определенное суждение о сложности всего бизнеса.
- При использовании нулевого объекта нужно писать комментарии, особенно в процессе рефакторинга, вызывающий должен быть уведомлен об окончании рефакторинга, потому что если вы введете этот паттерн и ваши коллеги не узнают об этом, они могут не написать логика для нулевого случая. Конечно, использование интерфейса может обойти эту ситуацию.
- Даже с интерфейсами общая сложность кода немного возрастает.
- Это не нулевой сценарий со строгой проверкой, и слепая имитация нулевого объекта усложнит дизайн.
- Не приговорите, чтобы опустошить этот сценарий, мы думаем об этом очень часто определить, пусто ли список, вы также можете воспользоваться таким подходом.