DRY 原则--设计模式

我们简单说就是这个dry原则是要保证减少这个代码的冗余和重复性的

DRY 原则(Don't Repeat Yourself):

DRY 原则强调避免代码重复,尽量将相似的
代码和逻辑提取到共享的方法、类或模块中。遵循 DRY 原则可以减少代码的冗余和
重复,提高代码的复用性和可维护性。当需要修改某个功能时,只需修改对应的共享
代码,而无需在多处进行相同的修改。这有助于降低维护成本,提高开发效率。
这条看似简单的原则,实则很难把控,设计原则不是1+1,有些代码在有些场景下就
是符合某些原则的,在其他场景下就是不符合的,我们学习了dry原则,不能狭隘的
理解。不是说只要两段代码长得一样,那就是违反 DRY 原则,同时有些看似不重复
的代码也有可能会违反DRY原则。

1、实现方法

在 Java 编程中,我们可以通过以下方法遵循 DRY 原则:
(1)使用方法(functions):当你发现自己在多处重复相同的代码时,可以将其
抽取为一个方法,并在需要的地方调用该方法。

public class DryExample {
public static void main(String[] args) {
printHello("张三");
printHello("李四");
}
public static void printHello(String name) {
System.out.println("你好," + name + "!");
}
}

在这个例子里面我们就去调用这个相关的函数,把这个原本应该去输出两遍的这个输出的相关的函数,保证我们去调用我们实现的这个方法就会使得我们的代码调用的变得更加简练和美观

可能你会说我们正常去写这个输出函数要比我们去实现这个输出的相关的方法简单好多啊

这样的话这个方法不就变的更加的复杂了吗

由于代码长度的过短,实现的相关的功能过于简单,使得我们没有感觉到dry的简单性,但是当我们所实现的功能真的变的强大的时候,这样的调用方法真的很减少很多我们敲代码的数量,这样的会使得我们的代码变得简单的很多

2)使用继承和接口:当多个类具有相似的行为时,可以使用继承和接口来抽象共
享的功能,从而减少重复代码。

public abstract class Animal {
public abstract void makeSound();
public void eat() {
System.out.println("动物在吃东西");
}
}
public class Dog extends Animal {
public void makeSound() {
System.out.println("汪汪");
}
}
public class Cat extends Animal {
public void makeSound() {
System.out.println("喵喵");
}
}

因为我们的子类继承了相关的父类

所以也使得我们这个子类获得了父类的相关的属性

(3)重用代码库和框架:使用成熟的代码库和框架可以避免从零开始编写一些通用
功能。例如,使用 Java 标准库、Apache Commons 或 Google Guava 等库。
遵循 DRY 原则可以帮助我们编写更高质量的代码,并更容易进行维护和扩展。同
时,要注意不要过度优化,以免影响代码的可读性和理解性。

2、只要两段代码长得一样,那就是违反 DRY 原则吗
不一定。DRY 原则的核心思想是减少重复代码,以提高代码的可维护性、可读性和
可重用性。然而,并不是所有看起来相似的代码都违反了 DRY 原则。在某些情况
下,重复的代码片段可能具有完全不同的逻辑含义,因此将它们合并可能会导致误
解。
在评估是否违反了 DRY 原则时,你需要考虑以下几点:
1. 逻辑一致性:如果两段代码的逻辑和功能是一致的,那么将它们合并为一个方法
或类是有意义的。如果它们实际上是在执行不同的任务,那么合并它们可能导致
难以理解的代码。
2. 可维护性:如果将两段看似相同的代码合并可能导致难以维护的代码(例如,增
加了过多的条件判断),那么保留一些重复可能是更好的选择。
3. 变更影响:考虑未来的需求变更。如果两段看似相同的代码很可能在未来分别发
生变化,那么将它们合并可能导致更多的维护负担。在这种情况下,保留一些重
复代码可能是更实际的选择。
总之,判断是否违反了 DRY 原则需要权衡多个因素。关键在于寻找适当的平衡点,
以提高代码质量,同时确保可维护性和可读性。
以下是一些从不同角度说明两段看似相似或相同的代码并不违反 DRY 原则的示例:

 1)不同的业务逻辑:
System.out.println("喵喵");
}
}
public class Example1 {
public static void main(String[] args) {
double priceAfterDiscount1 = applyClothingDiscount(100);
double priceAfterDiscount2 = applyElectronicsDiscount(100);
System.out.println("服装打折后的价格: " + priceAfterDiscount1);
尽管 applyClothingDiscount 和 applyElectronicsDiscount 方法的代码看
起来相似,我们甚至可以将他们合并成一个方法,当其实并不建议这样去做,因为它
们代表了不同的业务逻辑。因此,将它们合并并不符合 DRY 原则,结果这可能导致
代码难以理解和维护,比如以后想针对服装或者电器做特殊的折扣逻辑如何合并了就
会比较麻烦。
(2)专门用于不同目的的方法:
System.out.println("电子产品打折后的价格: " + priceAfterDiscount2);
}
// 服装折扣逻辑
public static double applyClothingDiscount(double originalPrice) {
return originalPrice * 0.9; // 10% 折扣
}
// 电子产品折扣逻辑
public static double applyElectronicsDiscount(double originalPrice) {
return originalPrice * 0.8; // 20% 折扣
}
}
public class Example2 {
public static void main(String[] args) {
double baseFare = 50;
double fareWithTax1 = addSalesTax(baseFare);
double fareWithTax2 = addVatTax(baseFare);
System.out.println("含销售税的费用: " + fareWithTax1);
System.out.println("含增值税的费用: " + fareWithTax2);
}
// 添加消费税
public static double addSalesTax(double baseFare) {
return baseFare + (baseFare * 0.05); // 5% 销售税
}
// 添加增值税
public static double addVatTax(double baseFare) {
return baseFare + (baseFare * 0.1); // 10% 增值税
}
虽然 addSalesTax 和 addVatTax 方法的代码看起来相似,代码结构几乎雷同,
但是他们两个都有不同目的,一个是计算增值税,一个计算消费税。在这种情况下,
将它们合并可能导致代码混乱,因此保留这些看似相似的代码段是合理的。
(3)针对不同数据类型的操作:
}
public class Example3 {
public static void main(String[] args) {
int[] intArray = {1, 2, 3, 4, 5};
double[] doubleArray = {1.1, 2.2, 3.3, 4.4, 5.5};
int intSum = sum(intArray);
double doubleSum = sum(doubleArray);
System.out.println("整数数组的和: " + intSum);
System.out.println("浮点数数组的和: " + doubleSum);
}
// 求整数数组之和
public static int sum(int[] array) {
int sum = 0;
for (int i : array) {
sum += i;
}
return sum;
}
// 求浮点数数组之和
public static double sum(double[] array) {
double sum = 0;
for (double d : array) {
sum += d;
}
return sum;
}
}
在这个示例中,我们有两个求和方法: sum(int[] array) 和 sum(double[]
array) 。虽然这两个方法的代码看起来相似,但它们处理的数据类型不同(一个处
理整数数组,另一个处理浮点数数组)。由于 Java 不支持泛型数组,我们不能简单
地使用一个泛型方法来替代这两个方法。因此,在这种情况下,保留这些看似相似的
代码段是合理的。
在这些示例中,我们展示了一些不同情况下看似相似或相同的代码,并没有违反
DRY 原则。这些例子说明了我们在实际编程中需要根据具体情况权衡各种因素,以
找到合适的平衡点。

三、什么样的代码违反了DRY原则呢?
1、功能重复
我们现在看一个例子。在同一个项目代码中有下面两个函数:validatePhone() 和
validatePhoneNum()。尽管两个函数的命名不同,实现逻辑不同,但功能是一样
的,都是用来校验手机号是否满足条件。
之所以在同一个项目中会有两个功能相同的函数,那是因为这两个函数是由两个不同
的小伙伴开发的,其中一个小伙伴在不知道已经有了 validatePhone() 的情况下,自
己又定义并实现了同样的函数。
一个小伙伴使用正则表达式来校验手机号:
另一个小伙伴不使用正则表达式来校验手机号:
/**
* 使用正则表达式验证手机号是否合法
*
* @param phoneNum 待验证的手机号
* @return boolean 验证结果,true表示合法,false表示不合法
*/
public static boolean validatePhone(String phoneNum) {
// 定义手机号的正则表达式
String regex = "^1[3456789]\\d{9}$";
// 利用String类的matches方法进行正则匹配
return phoneNum.matches(regex);
}
/**
* 不使用正则表达式验证手机号是否合法
*
* @param phoneNum 待验证的手机号
在这个例子中,我们的代码书写并不相同,但是他表达的含义,实现的功能却是重复
的,我们认为它违反了 DRY 原则。我们应该在项目中,应该将相同功能的方法统一
处理,如果不统一,将来手机校验规则一旦发生改变,而同学只知道
validatePhoneNum函数的存在,并对其做了修改,而使用validatePhone的地方就
可能留下祸患。
2、代码执行重复
我们再来看一个例子。其中,UserService 中 login() 函数用来校验用户登录是否成
功。如果失败,就返回异常;如果成功,就返回用户信息。具体代码如下所示:
* @return boolean 验证结果,true表示合法,false表示不合法
*/
public static boolean validatePhoneNum(String phoneNum) {
// 首先判断手机号长度是否为11位
if (phoneNum.length() != 11) {
return false;
}
// 然后逐个字符判断是否都是数字
for (int i = 0; i < phoneNum.length(); i++) {
char c = phoneNum.charAt(i);
if (c < '0' || c > '9') {
return false;
}
}
// 最后判断手机号是否以1开头
return phoneNum.startsWith("1");
}
public class UserService {
private UserDao UserDao;// 通过依赖注入或者 IOC 框架注入
public User login(String phone, String password) {
// 检查数据库是否存在该用户
boolean existed = UserDao.checkIfUserExisted(phone, password);
if (!existed) {
// ... throw AuthenticationFailureException...
}
// 通过手机查找用户
User user = UserDao.getUserByPhone(phone);
return user;
}
现在大家开始思考,以上的代码存在什么问题呢?
1、相同的代码有没有被执行多次?
2、相似的逻辑有没有被执行多次?
细心的同学一定发现了:
问题一:在checkIfUserExisted和getUserByPhone中都调用了
PhoneValidation.validate(phone)方法,这显然不满足DRY原则
问题二:查询用户的操作执行了两次,显然,这是一个重复操作,IO操作时很浪费资
源的,这一点一定要谨记。
修改如下,这其实也是我们日常写的最多的代码,只是如果我们不注意可能很多地方
就会写的出现这样或那样的问题:
}
public class UserDao {
// 检查用户是否存在
public boolean checkIfUserExisted(String phone, String password) {
if (!PhoneValidation.validate(phone)) {
// ... throw InvalidPhoneException...
}
if (!PasswordValidation.validate(password)) {
// ... throw InvalidPasswordException...
}
//...query db to check if phone&password exists...
}
// 去数据库查询用户
public User getUserByPhone(String phone) {
if (!PhoneValidation.validate(phone)) {
// ... throw InvalidPhoneException...
}
//...query db to get user by phone...
}
}
public class UserService {
private UserDao UserDao;// 通过依赖注入或者 IOC 框架注入
public User login(String phone, String password) {
if (!PhoneValidation.validate(phone)) {
小节:DRY原则(Don't Repeat Yourself),即不要重复自己的原则,是软件工程中
非常重要的一条设计原则。它强调在软件开发中避免重复代码,减少代码的冗余性,
提高代码的可维护性、可读性和可扩展性。DRY原则的核心思想是避免重复的代码,
尽可能将重复的代码封装成可重用的模块、函数、类等,提高代码的复用性,降低代
码的耦合性。
DRY原则有以下几个要点:
1. 避免重复的代码:尽可能减少代码的冗余和重复,将重复的代码封装成可重用的
函数、类、模块等。
2. 提高代码的可维护性:避免重复代码可以降低代码的冗余性和复杂度,提高代码
的可维护性和可读性。
3. 降低代码的耦合性:通过避免重复代码,可以减少代码之间的耦合度,提高代码
的灵活性和可扩展性。
4. 提高代码的复用性:通过将重复的代码封装成可重用的函数、类、模块等,提高
代码的复用性,减少代码的编写和维护成本。
// ... throw InvalidPhoneException...
}
if (!PasswordValidation.validate(password)) {
// ... throw InvalidPasswordException...
}
User user = UserDao.getUserByEmail(phone);
if (user == null || !password.equals(user.getPassword()) {
// ... throw AuthenticationFailureException...
}
return user;
}
}
public class UserDao {
public boolean checkIfUserExisted(String phone, String password) {
//...query db to check if phone&password exists
}
public User getUserByPhone(String phone) {
//...query db to get user by phone...
}
}

总之,DRY原则是一个非常实用的设计原则,可以在软件开发过程中帮助开发者减少
代码的冗余和重复,提高代码的可维护性和可读性,降低代码的耦合度和维护成本,
从而实现高效、可靠、可维护的软件系统。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值