侧边栏壁纸
博主头像
ZHD的小窝博主等级

行动起来,活在当下

  • 累计撰写 79 篇文章
  • 累计创建 53 个标签
  • 累计收到 1 条评论

目 录CONTENT

文章目录

Java语言设计7大原则和例子

江南的风
2014-06-10 / 0 评论 / 1 点赞 / 44 阅读 / 13874 字 / 正在检测是否收录...

Java开发设计原则主要包括七个核心原则,包括单一职责原则开放封闭原则里氏替换原则接口隔离原则依赖倒置原则迪米特法则合成复用原则,这些原则旨在提高代码的可读性、可维护性和可扩展性。

1. 单一职责原则

  • 定义:一个类只应该有一个引起变化的原因,即一个类只负责一个职责。

  • 目的:提高类的内聚性,降低类的耦合性。

  • 例子:假设我们有一个UserManager类,它最初负责用户信息的创建、更新、删除以及用户登录的验证。根据单一职责原则,我们可以将这个类拆分为两个类:UserServiceAuthenticationService

    • UserService类负责用户信息的创建、更新和删除。

    • AuthenticationService类负责用户登录的验证。

这样,每个类都只有一个明确的职责,使得代码更加清晰和易于维护。

// 单一职责原则示例  
// 假设我们有一个类负责处理用户信息和用户登录  
  
// 违反单一职责原则的版本(合并了用户服务和认证服务)  
// 这里只展示拆分后的版本  
  
// UserService.java  
public class UserService {  
    public void createUser(String username, String password) {  
        // 创建用户逻辑  
        System.out.println("User created: " + username);  
    }  
  
    public void updateUser(String username, String newPassword) {  
        // 更新用户逻辑  
        System.out.println("User updated: " + username);  
    }  
  
    // 其他用户管理功能...  
}  
  
// AuthenticationService.java  
public class AuthenticationService {  
    public boolean authenticate(String username, String password) {  
        // 认证逻辑  
        // 假设这里总是返回true作为示例  
        return true;  
    }  
  
    // 其他认证相关功能...  
}

2. 开放封闭原则

  • 定义:软件实体(类、模块、函数等)应当是可扩展的,但不可修改。

  • 目的:在不修改已有代码的情况下,通过扩展来实现新的功能。

  • 例子:考虑一个日志系统,它最初支持将日志信息输出到控制台。现在,我们需要添加对文件日志的支持。

    • 我们可以定义一个Logger接口,并创建一个ConsoleLogger类实现该接口用于控制台日志。

    • 当需要添加文件日志时,我们不需要修改ConsoleLoggerLogger接口,而是创建一个新的FileLogger类实现Logger接口。

这样,我们的日志系统就遵循了开放封闭原则,它对新的日志类型(如文件日志)是开放的,但对已有的日志类型(如控制台日志)是封闭的。

// 开放封闭原则示例  
// 假设我们有一个日志系统,支持多种日志级别  
  
// Logger.java(接口)  
public interface Logger {  
    void log(String message);  
}  
  
// ConsoleLogger.java(实现)  
public class ConsoleLogger implements Logger {  
    @Override  
    public void log(String message) {  
        System.out.println("Console: " + message);  
    }  
}  
  
// FileLogger.java(新添加的实现,遵循OCP)  
public class FileLogger implements Logger {  
    @Override  
    public void log(String message) {  
        // 假设这里将消息写入文件  
        System.out.println("File: " + message); // 仅作为示例  
    }  
}  
  
// LogManager.java(使用Logger接口)  
public class LogManager {  
    private Logger logger;  
  
    public LogManager(Logger logger) {  
        this.logger = logger;  
    }  
  
    public void logMessage(String message) {  
        logger.log(message);  
    }  
  
    // 可以根据需要切换不同的Logger实现  
}

3. 里氏替换原则

  • 定义:子类必须能够替换其父类,且替换后不会影响程序的正确性。

  • 目的:确保子类在替换父类时不会引入错误的行为。

  • 示例:考虑一个Rectangle类继承自Shape类,并实现了计算面积的方法。

    • Shape类有一个getArea()方法,用于计算形状的面积。

    • Rectangle类重写了getArea()方法,以计算矩形的面积。

    根据里氏替换原则,任何使用Shape类型对象的地方都应该能够无缝地使用Rectangle类型的对象替换,而不会导致错误。这意味着Rectangle类的getArea()方法必须正确实现,以确保其行为与Shape类的预期一致。

// 假设有一个Shape接口和一个Rectangle类  
  
// Shape.java  
public interface Shape {  
    double getArea();  
}  
  
// Rectangle.java  
public class Rectangle implements Shape {  
    private double width;  
    private double height;  
  
    public Rectangle(double width, double height) {  
        this.width = width;  
        this.height = height;  
    }  
  
    @Override  
    public double getArea() {  
        return width * height;  
    }  
  
    // Rectangle类正确地实现了Shape接口,因此可以替换Shape接口的任何引用  
}

4. 接口隔离原则

  • 定义:客户端不应该依赖它不需要的接口,即一个类对另一个类的依赖应该建立在最小的接口上。

  • 目的:将接口拆分成多个独立的接口,避免接口过于臃肿。

  • 示例:考虑一个打印系统,最初有一个Printable接口,包含了打印文本、打印图片和打印PDF的方法。

    • 根据接口隔离原则,我们可以将这个接口拆分为三个更小的接口:TextPrintableImagePrintablePdfPrintable

    • 这样,不同的类(如TextPrinterImagePrinterPdfPrinter)就可以只实现它们需要的接口,而不是被迫实现整个Printable接口。

// 接口隔离原则示例  
  
// Printable.java(过于宽泛的接口)  
// 假设这个接口被拆分了  
  
// TextPrintable.java  
public interface TextPrintable {  
    void printText(String text);  
}  
  
// ImagePrintable.java  
public interface ImagePrintable {  
    void printImage(byte[] imageData);  
}  
  
// 具体的打印类将实现这些更具体的接口

5. 依赖倒置原则

  • 定义:高层模块不应该依赖低层模块,它们都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。

  • 目的:将依赖关系倒置,使高层模块和低层模块之间的依赖关系更加灵活和可维护。

  • 示例:在一个邮件发送系统中,我们有一个EmailSender类依赖于具体的邮件服务(如SMTP服务)。

    • 根据依赖倒置原则,我们应该定义一个IEmailService接口,让EmailSender类依赖于这个接口而不是具体的邮件服务类。

    • 然后,我们可以创建多个实现IEmailService接口的类(如SmtpEmailServiceGmailEmailService等),以便在需要时轻松切换邮件服务。

// 依赖倒置原则示例  
  
// IEmailService(接口)  
public interface IEmailService {  
    void sendEmail(String to, String subject, String body);  
}  
  
// SmtpEmailService(实现)  
public class SmtpEmailService implements IEmailService {  
    @Override  
    public void sendEmail(String to, String subject, String body) {  
        // SMTP发送逻辑  
        System.out.println("Sending email via SMTP to: " + to);  
    }  
}  

// ApiEmailService(实现) 
public class ApiEmailService implements IEmailService {  
    @Override  
    public void sendEmail(String to, String subject, String body) {  
        // API发送逻辑  
        System.out.println("Sending email via API to: " + to);  
    }  
}
  
// EmailSender.java(依赖于接口)  
public class EmailSender {  
    private IEmailService emailService;  
  
    public EmailSender(IEmailService emailService) {  
        this.emailService = emailService;  
    }  
  
    public void send(String to, String subject, String body) {  
        emailService.sendEmail(to, subject, body);  
    }  
}  
  
// 客户端代码示例  
public class Application {  
    public static void main(String[] args) {  
        // 创建具体的Email服务实现  
        IEmailService emailService = new SmtpEmailService();  
  
        // 依赖注入Email服务到EmailSender  
        EmailSender emailSender = new EmailSender(emailService);  
  
        // 使用EmailSender发送邮件  
        emailSender.send("user@example.com", "Hello Subject", "Hello Body");  
  
        // 如果需要更换邮件发送方式(比如使用API发送),只需更换IEmailService的实现即可  
        // IEmailService apiEmailService = new ApiEmailService();  
        // EmailSender apiEmailSender = new EmailSender(apiEmailService);  
        // apiEmailSender.send("user@example.com", "API Subject", "API Body");  
    }  
}  
  

6. 迪米特法则

  • 定义:一个对象应该对其他对象有最少的了解,即一个软件实体应当尽可能少地与其他实体发生相互作用。

  • 目的:降低对象之间的耦合度,提高系统的可维护性。

  • 示例:比如我们要设计一个打印机系统,Printer 和 Scanner 类分别实现了 Device 接口,表示它们是不同类型的设备。DeviceManager 类作为中介者,负责管理这些设备。在 operateAllDevices 方法中,DeviceManager 只与 Device 接口进行交互,而不直接与 Printer 或 Scanner 类进行交互。这样,就降低了类之间的耦合度,提高了系统的可维护性和可扩展性。

// 定义接口Device,表示设备  
interface Device {  
    void operate();  
}  
  
// 打印机类,实现Device接口  
class Printer implements Device {  
    @Override  
    public void operate() {  
        System.out.println("Printing...");  
    }  
}  
  
// 扫描仪类,实现Device接口  
class Scanner implements Device {  
    @Override  
    public void operate() {  
        System.out.println("Scanning...");  
    }  
}  
  
// 设备管理类,作为中介者,管理所有设备  
class DeviceManager {  
    private List<Device> devices = new ArrayList<>();  
  
    public void addDevice(Device device) {  
        devices.add(device);  
    }  
  
    public void operateAllDevices() {  
        for (Device device : devices) {  
            device.operate(); // DeviceManager 只与 Device 接口进行交互,不直接与具体实现类交互  
        }  
    }  
}  
  
// 主类  
public class Main {  
    public static void main(String[] args) {  
        DeviceManager deviceManager = new DeviceManager();  
        deviceManager.addDevice(new Printer());  
        deviceManager.addDevice(new Scanner());  
        deviceManager.operateAllDevices();  
    }  
}

7. 合成复用原则

  • 定义:尽量使用对象组合/聚合的方式来实现软件复用,而不是通过继承关系。

  • 目的:提高代码的灵活性和可维护性。

  • 示例:假设我们有一个系统需要处理不同类型的媒体文件(如音频、视频等),并且每种媒体文件都有播放的功能。不使用合成复用原则,我们可能会为每种媒体类型创建一个子类并实现播放方法。然而,使用合成复用原则,我们可以将播放功能抽象为一个接口或抽象类,并通过组合不同的实现类来实现媒体文件的播放。

    • 不使用合成复用原则(通过继承方式)

    // 假设的基类  
    class Media {  
        // 基类方法  
    }  
      
    // 音频类,继承自Media  
    class Audio extends Media {  
        void play() {  
            System.out.println("Playing audio file");  
        }  
    }  
      
    // 视频类,继承自Media  
    class Video extends Media {  
        void play() {  
            System.out.println("Playing video file");  
        }  
    }  
      
    // 客户端代码  
    public class Client {  
        public static void main(String[] args) {  
            Audio audio = new Audio();  
            audio.play();  
      
            Video video = new Video();  
            video.play();  
        }  
    }
    • 使用合成复用原则

// 播放接口  
interface Playable {  
    void play();  
}  
  
// 音频播放实现  
class AudioPlayer implements Playable {  
    @Override  
    public void play() {  
        System.out.println("Playing audio file");  
    }  
}  
  
// 视频播放实现  
class VideoPlayer implements Playable {  
    @Override  
    public void play() {  
        System.out.println("Playing video file");  
    }  
}  
  
// 媒体类,通过组合Playable接口  
class Media {  
    private Playable playable;  
  
    public Media(Playable playable) {  
        this.playable = playable;  
    }  
  
    void play() {  
        playable.play();  
    }  
}  
  
// 客户端代码  
public class Client {  
    public static void main(String[] args) {  
        Media audioMedia = new Media(new AudioPlayer());  
        audioMedia.play();  
  
        Media videoMedia = new Media(new VideoPlayer());  
        videoMedia.play();  
    }  
}

1

评论区