六大设计原则,也称为SOLID原则,是一组编程指导原则,它们可以帮助我们编写易于维护和扩展的高质量代码。这些原则是:
一个类应该只有一个责任,也就是说,它只应该有一个引起它变化的原因。
public class UserManager {
public void register(User user) {
// 注册用户
}
public void changePassword(User user, String newPassword) {
// 修改密码
}
// 其他用户管理相关方法...
}
public class UserAuthenticator {
public void login(String username, String password) {
// 用户登录
}
// 其他用户认证相关方法...
}
public class EmailSender {
public void sendEmail(User user, String subject, String message) {
// 发送邮件
}
// 其他邮件相关方法...
}
public class SMSSender {
public void sendSMS(User user, String message) {
// 发送短信
}
// 其他短信相关方法...
}
上面的代码实现了用户的注册、登录、修改密码、发送邮件和发送短信等多个功能。这个类的职责不仅包括管理用户,还包括与用户的交互和通信,这违反了 SRP 原则。
为了符合 SRP,我们需要将这个类的职责分解成多个独立的类,每个类都只负责单一职责。例如,我们可以创建以下几个类:
public class UserManager {
public void register(User user) {
// 注册用户
}
public void changePassword(User user, String newPassword) {
// 修改密码
}
// 其他用户管理相关方法...
}
public class UserAuthenticator {
public void login(String username, String password) {
// 用户登录
}
// 其他用户认证相关方法...
}
public class EmailSender {
public void sendEmail(User user, String subject, String message) {
// 发送邮件
}
// 其他邮件相关方法...
}
public class SMSSender {
public void sendSMS(User user, String message) {
// 发送短信
}
// 其他短信相关方法...
}
现在,每个类都只负责单一职责,代码更加清晰、简洁和易于维护。如果要增加或修改某个功能,只需要修改相应的类,而不会影响其他类的功能。这样,我们就遵循了 SRP 原则,使代码更加健壮和可扩展。
软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。
public class Circle {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
public double getRadius() {
return radius;
}
public double getArea() {
return Math.PI * radius * radius;
}
// 其他方法...
}
上面的代码实现了一个圆形类,包括计算圆的半径和面积等方法。现在,我们要增加一个新的功能,计算圆的周长。
如果直接在 Circle 类中添加一个新的方法,那么就违反了 OCP 原则,因为这样会修改已有代码。为了符合 OCP,我们需要通过扩展来增加新的功能,而不是修改已有代码。
我们可以创建一个新的接口 Shape,其中包括计算面积、周长等方法:
public interface Shape {
double getArea();
double getPerimeter();
}
然后,我们可以创建一个新的类 Circle2,实现 Shape 接口并包括计算圆的周长等方法:
public class Circle2 implements Shape {
private double radius;
public Circle2(double radius) {
this.radius = radius;
}
public double getRadius() {
return radius;
}
public double getArea() {
return Math.PI * radius * radius;
}
public double getPerimeter() {
return 2 * Math.PI * radius;
}
// 其他方法...
}
这样,我们就可以通过扩展来增加新的功能,而不是修改已有代码。客户端只依赖于 Shape 接口而不依赖于具体实现,这也符合 OCP 的要求。如果我们要增加其他形状的类,只需要实现 Shape 接口即可,而不需要修改 Circle 类。
总结一下,OCP 要求我们尽可能避免修改已有代码,通过扩展来增加新的功能。为了实现 OCP,我们可以尽可能高度封装类的实现细节,基于抽象接口而非具体实现进行设计,遵循依赖倒置原则,避免出现分支语句等。
在使用继承关系的类之间,子类必须能够完全替换父类并且保持程序的正确性。
场景示例:
在商城项目中,推出了三种促销方式:
满减活动,两百以上打八折
打折活动,全场九折
返现活动,消费超1000,返200
计一个结算接口及其实现类,该接口的结算方法能够对用户的最终消费金额进行一个计算.计算结果需要满足上面的多种促销方式。
我们可以定义一个接口 Discount 来实现三种不同的促销方式。这个接口中有一个方法 calculateDiscount(),子类必须实现它。
public interface Discount {
double calculateDiscount(double price);
}
接下来,我们可以定义三个子类来实现 Discount 接口来实现三种不同的促销方式。
//满减活动,两百以上打八折
class FullDiscount implements Discount {
private static final double FULL_PRICE = 200.0;
private static final double DISCOUNT_RATE = 0.8;
@Override
public double calculateDiscount(double price) {
return price >= FULL_PRICE ? price * DISCOUNT_RATE : price;
}
}
//打折活动,全场九折
class PercentageDiscount implements Discount {
private static final double DISCOUNT_RATE = 0.9;
@Override
public double calculateDiscount(double price) {
return price * DISCOUNT_RATE;
}
}
//返现活动,消费超1000,返200
class CashBackDiscount implements Discount {
private static final double FULL_PRICE = 1000.0;
private static final double CASH_BACK = 200.0;
@Override
public double calculateDiscount(double price) {
return price >= FULL_PRICE ? price - CASH_BACK : price;
}
}
最后,可以通过创建不同的 Discount 子类对象,将其传递给 ShoppingCart 构造函数中,来实现不同的促销方式。
public class ShoppingCart {
private final Discount discount;
public ShoppingCart(Discount fullDiscount) {
this.discount = fullDiscount;
}
public double cul(){
return this.discount.calculateDiscount(3000);
}
public static void main(String[] args) {
Discount fullDiscount = new FullDiscount();
ShoppingCart shoppingCart1 = new ShoppingCart(fullDiscount);
double result1 = shoppingCart1.cul();
Discount percentageDiscount = new PercentageDiscount();
ShoppingCart shoppingCart2 = new ShoppingCart(percentageDiscount);
double result2 = shoppingCart2.cul();
Discount cashBackDiscount = new CashBackDiscount();
ShoppingCart shoppingCart3 = new ShoppingCart(cashBackDiscount);
double result3 = shoppingCart3.cul();
}
}
客户端不应该依赖于它不需要的接口。换句话说,一个类不应该被强迫实现它不需要的方法。
// 接口1:定义飞行行为
interface Flyable {
void fly();
}
// 接口2:定义游泳行为
interface Swimable {
void swim();
}
// 接口3:定义跳跃行为
interface Jumpable {
void jump();
}
// 实现类:鸟类
class Bird implements Flyable, Jumpable {
@Override
public void fly() {
System.out.println("I am flying.");
}
@Override
public void jump() {
System.out.println("I am jumping.");
}
}
// 实现类:鱼类
class Fish implements Swimable {
@Override
public void swim() {
System.out.println("I am swimming.");
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
// 鸟类只需要依赖飞行和跳跃的接口,而不需要依赖游泳的接口
Flyable bird = new Bird();
bird.fly();
Jumpable bird2 = new Bird();
bird2.jump();
// 鱼类只需要依赖游泳的接口,而不需要依赖飞行和跳跃的接口
Swimable fish = new Fish();
fish.swim();
}
}
在这个例子中,我们定义了三个接口:Flyable、Swimable、Jumpable,分别定义了飞行、游泳和跳跃的行为。我们实现了两个具体的类:Bird和Fish,分别实现了不同的接口。
在客户端代码中,我们只依赖所需的接口,而不是依赖具体的类。这样,我们就可以遵循接口隔离原则,将依赖关系降到最小,减少了代码的耦合性,提高了系统的可维护性和可扩展性。
高层模块不应该依赖于低层模块,它们都应该依赖于抽象。抽象不应该依赖于细节,细节应该依赖于抽象。
// 定义一个接口:开关
interface Switchable {
void turnOn();
void turnOff();
}
// 低层模块:灯
class Light implements Switchable {
@Override
public void turnOn() {
System.out.println("Light is on");
}
@Override
public void turnOff() {
System.out.println("Light is off");
}
}
// 高层模块:电视
class TV {
private Switchable switchable;
public TV(Switchable switchable) {
this.switchable = switchable;
}
public void turnOn() {
switchable.turnOn();
}
public void turnOff() {
switchable.turnOff();
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
// 创建一个灯对象
Light light = new Light();
// 创建一个电视对象,它依赖于灯对象
TV tv = new TV(light);
// 打开电视
tv.turnOn();
// 关闭电视
tv.turnOff();
}
}
在这个例子中,我们定义了一个接口Switchable,其中定义了两个方法:turnOn和turnOff。我们创建了一个低层模块Light,它实现了Switchable接口。我们创建了一个高层模块TV,它依赖于Switchable接口而不是具体的实现类。在客户端代码中,我们创建了一个Light对象并将它作为参数传递给TV构造函数。这样,TV就可以控制灯的开关状态。
在这个例子中,我们遵循了接口依赖倒置原则,TV高层模块依赖于Switchable抽象,而不是具体的实现类Light。这样,我们可以轻松地替换掉Light实现类,例如换成一个新的Fan实现类,而不需要修改TV类的代码。这样,我们提高了代码的可维护性和可扩展性。
一个对象应该对其他对象保持最少的了解。也就是说,一个类不应该知道太多关于其他类的信息,它只需要知道与它交互的类的接口即可。
// 定义一个房间类
class Room {
private List furnitureList;
public Room() {
furnitureList = new ArrayList<>();
furnitureList.add(new Bed());
furnitureList.add(new Desk());
}
public void showFurniture() {
for (Furniture furniture : furnitureList) {
furniture.show();
}
}
}
// 定义一个家具抽象类
abstract class Furniture {
abstract void show();
}
// 定义一个床类
class Bed extends Furniture {
@Override
void show() {
System.out.println("This is a bed.");
}
}
// 定义一个桌子类
class Desk extends Furniture {
@Override
void show() {
System.out.println("This is a desk.");
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Room room = new Room();
room.showFurniture();
}
}
在这个例子中,我们定义了一个Room类,它包含了一些家具对象,例如床、桌子。我们将家具抽象为Furniture类,并让床、桌子和椅子都继承自Furniture类。在客户端代码中,我们创建了一个Room对象并调用showFurniture方法来展示房间中的所有家具。
这个例子遵循了迪米特法则,Room类只与它直接的朋友通信,即家具对象。Room类不需要知道家具对象的具体实现,只需要知道它们都实现了Furniture抽象类即可。这样,我们可以方便地增加或者减少家具对象,而不需要修改Room类的代码。这样,我们提高了代码的可维护性和可扩展性。
以上就是六大设计原则的讲解,这些设计原则可以帮助我们编写出易于维护、易于扩展、低耦合、高内聚的代码。