- 作者:老汪软件技巧
- 发表时间:2024-09-27 00:02
- 浏览量:
概述
面向对象设计原则是学习设计模式的基础,每一种设计模式都符合某一种或多种面向对象原则。通过在软件开发中使用这些原则,可以提高软件的可维护性和可复用性,让我们可以设计出更加灵活也更容易扩展的软件系统,实现可维护性复用的目标。
面向对象七大原则单一职责原则开闭原则里氏替换原则依赖倒转原则接口隔离原则合成复用原则迪米特法则单一职责原则实例
某基于Java的C/S系统的“登录功能”通过如下登录类(Login)实现
classDiagram
class Login
Login : + init() void
Login : + display() void
Login : + validate() void
Login : + getConnection() Connection
Login : + findUser(String name, String pwd) boolean
Login : + main() void
说明使用单一职责原则对其进行重构
拆分为如下4个类
LoginForm 负责界面展示,它只包含与界面有关的方法和事件处理方法UserDAO 负责用户表的CRUD操作DBUtil 负责数据库的连接MainClass 负责启动系统
重构后类图如下
classDiagram
class MainClass{
+ main(String args[]) void
}
class LoginForm{
- dao : UserDAO
+ init() void
+ display() void
+ validate() void
}
class UserDAO{
- db : DBUtil
+ findUser(String name, String pwd) boolean
}
class DBUtil{
+ getConnection() Connection
}
MainClass ..> LoginForm
LoginForm --> UserDAO
UserDAO --> DBUtil
总结
通过单一职责原则重构后系统中类的个数增加,但是类的复用性很好。DBUtil类可供多个DAO类使用,而UserDAO类也可供多个界面类使用,一个类的修改不会对其他类产生影响,系统的可维护性也将增强
开闭原则实例说明
某图形界面系统提供了各种不同形状的按钮,客户端代码可针对这些按钮进行编程,用户可能会改变需求,要求使用不同的按钮,原始设计方案如下:
classDiagram
direction LR
class LoginForm{
- button : CircleButton
+ display() void
}
class CircleButton{
+ display() void
}
LoginForm --> CircleButton
classDiagram
direction LR
class LoginForm{
- button : RectangleButton
+ display() void
}
class RectangleButton{
+ display() void
}
LoginForm --> RectangleButton
如果界面类 LoginForm需要将圆形按钮(CircleButton)改为矩形按钮(RectangleButton),则需要修改LoginForm的源代码,修改按钮类的类名,由于圆形按钮和矩形按钮的显示方法不相同,因此还需要修改LoginForm的display()方法
使用开闭原则重构
增加一个抽象类,只需要修改配置文件,不需要修改类的源代码,即可使用不同的按钮
classDiagram
direction LR
class LoginForm{
- button : RectangleButton
+ display() void
}
class CircleButton{
+ display() void
}
class RectangleButton{
+ display() void
}
class AbstracButton{
+ display() void
}
LoginForm --> AbstracButton
CircleButton --|> AbstracButton
RectangleButton --|> AbstracButton
里氏替换原则注意事项如果一个方法只存在子类中,父类中不提供相应的声明,则无法在父类对象中直接使用该方法使用里氏替换原则时,尽量把父类设计为抽象类或接口里氏替换原则实例
class Animal {
public void makeSound() {
System.out.println("动物发出声音");
}
}
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("汪汪汪");
}
}
class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("喵喵喵");
}
}
class Test {
public static void main(String[] args) {
Animal animal1 = new Dog();
Animal animal2 = new Cat();
animal1.makeSound();
animal2.makeSound();
}
}
在这个例子中,Dog和Cat类都是Animal类的子类。在main方法中,我们可以将Dog和Cat的对象赋值给Animal类型的变量,这体现了里氏替换原则,即子类可以替换父类出现在任何父类能够出现的地方,并且不会产生错误的行为。当调用makeSound方法时,会根据实际的对象类型执行相应子类的方法。
依赖倒转原则
以一个电商系统为例:
假设没有遵循依赖倒转原则:
遵循依赖倒转原则:
接口隔离原则
假设我们正在开发一个图形绘制系统,有不同类型的图形需要绘制,如圆形(Circle)、矩形(Rectangle)和三角形(Triangle)。
不遵循接口隔离原则的情况:
interface Shape {
void drawCircle();
void drawRectangle();
void drawTriangle();
}
class Circle implements Shape {
@Override
public void drawCircle() {
// 绘制圆形的代码
}
@Override
public void drawRectangle() {
// 什么也不做,因为圆形不需要绘制矩形的方法
}
@Override
public void drawTriangle() {
// 什么也不做,因为圆形不需要绘制三角形的方法
}
}
class Rectangle implements Shape {
@Override
public void drawCircle() {
// 什么也不做,因为矩形不需要绘制圆形的方法
}
@Override
public void drawRectangle() {
// 绘制矩形的代码
}
@Override
public void drawTriangle() {
// 什么也不做,因为矩形不需要绘制三角形的方法
}
}
class Triangle implements Shape {
@Override
public void drawCircle() {
// 什么也不做,因为三角形不需要绘制圆形的方法
}
@Override
public void drawRectangle() {
// 什么也不做,因为三角形不需要绘制矩形的方法
}
@Override
public void drawTriangle() {
// 绘制三角形的代码
}
}
遵循接口隔离原则的情况:
interface CircleShape {
void drawCircle();
}
interface RectangleShape {
void drawRectangle();
}
interface TriangleShape {
void drawTriangle();
}
class Circle implements CircleShape {
@Override
public void drawCircle() {
// 绘制圆形的代码
}
}
class Rectangle implements RectangleShape {
@Override
public void drawRectangle() {
// 绘制矩形的代码
}
}
class Triangle implements TriangleShape {
@Override
public void drawTriangle() {
// 绘制三角形的代码
}
}
合成复用原则
假设我们正在开发一个图形绘制系统,有不同类型的图形需要绘制,并且我们希望能够对图形进行移动和缩放操作。
不遵循合成复用原则,过度使用继承的情况:
class Shape {
public void draw() {
// 绘制图形的通用方法
}
}
class Circle extends Shape {
@Override
public void draw() {
// 绘制圆形的具体方法
}
}
class Rectangle extends Shape {
@Override
public void draw() {
// 绘制矩形的具体方法
}
}
class MoveableShape extends Shape {
public void move() {
// 移动图形的方法
}
}
class ScalableShape extends Shape {
public void scale() {
// 缩放图形的方法
}
}
在这个例子中,为了给图形添加移动和缩放功能,我们使用了继承。但是这样会导致一些问题,比如如果父类Shape的draw方法发生改变,所有的子类包括MoveableShape和ScalableShape都可能受到影响。而且,如果我们有更多的功能需要添加,就需要不断地创建新的子类,导致类的层次结构变得复杂。
遵循合成复用原则,使用组合的情况:
interface Drawable {
void draw();
}
interface Moveable {
void move();
}
interface Scalable {
void scale();
}
class Circle implements Drawable {
@Override
public void draw() {
// 绘制圆形的具体方法
}
}
class Rectangle implements Drawable {
@Override
public void draw() {
// 绘制矩形的具体方法
}
}
class MoveableDecorator implements Moveable {
private Moveable decoratedShape;
public MoveableDecorator(Moveable decoratedShape) {
this.decoratedShape = decoratedShape;
}
@Override
public void move() {
// 移动图形的方法
decoratedShape.move();
}
}
class ScalableDecorator implements Scalable {
private Scalable decoratedShape;
public ScalableDecorator(Scalable decoratedShape) {
this.decoratedShape = decoratedShape;
}
@Override
public void scale() {
// 缩放图形的方法
decoratedShape.scale();
}
}
在这个例子中,我们使用了接口来定义不同的行为,然后通过组合的方式将这些行为动态地添加到图形对象上。如果我们需要一个可移动的圆形,我们可以这样创建:
Moveable moveableCircle = new MoveableDecorator(new Circle());
moveableCircle.move();
如果我们需要一个可移动和可缩放的矩形,我们可以这样创建:
Scalable scalableRectangle = new ScalableDecorator(new Rectangle());
Moveable moveableScalableRectangle = new MoveableDecorator(scalableRectangle);
moveableScalableRectangle.move();
scalableRectangle.scale();
迪米特法则
只依赖直接相关的对象,不依赖间接相关的对象,通过已经依赖的对象去访问间接相关的对象
如下文中的通过student通过course去访问teacher信息
假设我们有一个学校管理系统,其中有学生(Student)、教师(Teacher)和课程(Course)三个类。
不遵循迪米特法则的情况:
class Student {
private String name;
private Course[] courses;
public Student(String name) {
this.name = name;
}
public void addCourse(Course course) {
// 添加课程的方法
//...
}
public void showTeacherInfo(Teacher teacher) {
// 直接访问教师的信息,违反迪米特法则
System.out.println("Teacher " + teacher.getName() + " teaches " + teacher.getCoursesTaught());
}
}
class Teacher {
private String name;
private Course[] coursesTaught;
public Teacher(String name) {
this.name = name;
}
public String getName() {
return name;
}
public Course[] getCoursesTaught() {
return coursesTaught;
}
}
class Course {
private String name;
public Course(String name) {
this.name = name;
}
}
在这个例子中,Student类直接访问了Teacher类的内部信息,违反了迪米特法则。这样会导致Student类和Teacher类之间的耦合度增加,当Teacher类的内部实现发生变化时,Student类可能也需要进行修改。
遵循迪米特法则的情况:
class Student {
private String name;
private Course[] courses;
public Student(String name) {
this.name = name;
}
public void addCourse(Course course) {
// 添加课程的方法
//...
}
public void showTeacherInfo(Teacher teacher, CourseService courseService) {
// 通过课程服务类间接获取教师信息,遵循迪米特法则
System.out.println("Teacher " + courseService.getTeacherNameByCourse(teacher) + " teaches " + courseService.getCoursesTaughtByTeacher(teacher));
}
}
class Teacher {
private String name;
private Course[] coursesTaught;
public Teacher(String name) {
this.name = name;
}
public String getName() {
return name;
}
public Course[] getCoursesTaught() {
return coursesTaught;
}
}
class Course {
private String name;
public Course(String name) {
this.name = name;
}
}
class CourseService {
public String getTeacherNameByCourse(Teacher teacher) {
// 根据课程获取教师名称的方法
//...
return teacher.getName();
}
public Course[] getCoursesTaughtByTeacher(Teacher teacher) {
// 获取教师所教课程的方法
//...
return teacher.getCoursesTaught();
}
}
在这个改进后的例子中,Student类不再直接访问Teacher类的内部信息,而是通过一个中间的CourseService类来获取教师的信息。这样降低了Student类和Teacher类之间的耦合度,当Teacher类的内部实现发生变化时,Student类不需要进行修改。