- 用抽象构建框架,用实现扩展细节。
- 不以改动原有类的方式来实现新需求,而是应该以实现事先抽象出来的接口(或具体类继承抽象类)的方式来实现。
增加了程序的可扩展性,降低了程序的维护成本。
设计一个在线课程类:
由于教学资源有限,开始的时候只有类似于博客的,通过文字讲解的课程。 但是随着教学资源的增多,后来增加了视频课程,音频课程以及直播课程。
public class Open_Close_Principle //开闭原则
{
public class Course
{
//课程名
public string CourseName { set; get; }
//课程介绍
public string CourseIntroduction { set; get; }
//教师名
public string TeacherName { set; get; }
//博客课程
public void ContentCourse(){}
public override string ToString()
{
return $"课程名:{CourseName}, 课程介绍:{CourseIntroduction}, 教师名称:{TeacherName}";
}
//=================新需求==================
//新需求:视频课程
public void VideoCourse(){}
//新需求:音频课程
public void AudioCourse(){}
//新需求:直播课程
public void LiveCourse(){}
}
}
public class Open_Close_Principle //开闭原则
{
public abstract class Course //抽象类:对类抽象,提取共有的有参或无参的方法及属性,可含方法体
{
//课程名
public string CourseName { set; get; }
//课程介绍
public string CourseIntroduction { set; get; }
//教师名
public string TeacherName { set; get; }
//重写ToString()方法
public override string ToString()
{
return $"课程名:{CourseName}, 课程介绍:{CourseIntroduction}, 教师名称:{TeacherName}";
}
}
//接口:对行为抽象,提取共有方法,但具体实现交给子类
public interface CourseAction
{
void ClassBegin();
void ClassOver();
}
//博客课程,继承抽象类和接口
public class ContentCourse : Course,CourseAction
{
//无参构造
public ContentCourse() { }
//有参构造
public ContentCourse(string courseName,string courseIntroduction,string teacherName)
{
this.CourseName = courseName;
this.CourseIntroduction = courseIntroduction;
this.TeacherName = teacherName;
}
//接口实现
public void ClassBegin()
{
Console.WriteLine("博客课程上课");
}
public void ClassOver()
{
Console.WriteLine("博客课程下课");
}
//子类定义的方法
public string Content() { return "博客内容"; }
}
//视频课程
public class VideoCourse : Course { }
//音频课程
public class AudioCourse : Course { }
//直播课程
public class LiveCourse : Course { }
}
- 如果一个类有多种职责,就会有多种原因导致这个类发生变化,从而导致维护困难。
- 在开发过程中如果发现当前类的职责不仅仅有一个,就应该将本不属于该类真正的职责分离出去。
- 不单指类,函数方法也要遵循。
1.初始需求:创造一个员工类,这个类含有员工的一些基本信息。
2. 新需求:增添两个方法①判断员工今年是否升职;②计算员工薪水
public class Single_Responsibility_Principle //单一职责原则
{
public class Employee //员工类
{
//员工号
public long EmployeeID { set; get; }
//员工姓名
public string Name { set; get; }
//员工性别
public string Sex { set; get; }
//员工工龄
public int WorkAge { set; get; }
//员工薪水
public decimal Salary { set; get; }
//=============新需求==================
//计算薪水
public decimal CaculateSalary() { return 0; }
//今年是否升职
public bool GetPromotionThisYear() { return false; }
}
}
public class Single_Responsibility_Principle //单一职责原则
{
public class Employee //员工类
{
//员工号
public int EmployeeID { set; get; }
//员工姓名
public string Name { set; get; }
//员工性别
public string Sex { set; get; }
//员工工龄
public int WorkAge { set; get; }
//员工薪水
public decimal Salary { set; get; }
}
public class FinancialApartment //财务部门负责薪资计算
{
public decimal CaculateSalary(Employee employee) { return 0; }
}
public class HRApartment //人事部门负责员工的晋升
{
public bool GetPromotionThisYear(Employee employee) { return false; }
}
}
- 针对接口编程,而不是针对实现编程。
- 尽量不要从具体的类派生,而是以继承抽象类或实现接口来实现。
- 关于高层模块与低层模块的划分可以按照决策能力的高低进行划分。业务层自然就处于高层模块,逻辑层和数据层自然就归类为底层。
- 倒置的意思:
在设计角度:高层与底层的构思顺序倒置,本来由下往上,倒置后由上往下。
在开发者和程序角度:底层的依赖倒置,不再依赖开发者,而是依赖程序本身来实现。
以“顾客购物程序”为例:
设计顾客类和商店类,商店类中定义了Sell()方法,顾客通过不同的商店实现不同的购物。
public class Dependency_Inversion_Principle //依赖倒置原则
{
public class WandaShop
{
public string Sell() { return "这里是万达商场"; }
}
public class WoermaShop
{
public string Sell() { return "这里是沃尔玛商场"; }
}
///
/// 顾客类:
/// 当商场变化时,总要反复修改此处商场类型,违反了开闭原则。
/// 具体原因是因为顾客类与具体的商店类绑定在了一起,违背了依赖倒置原则。
///
public class Customer
{
public void Shopping(WandaShop shop)
{
shop.Sell();
}
}
}
public class Dependency_Inversion_Principle //依赖倒置原则
{
//Shop商场接口
public interface Shop
{
string Sell();
}
//顾客类,定义Shopping方法
public class Customer
{
public void Shopping(Shop shop)
{
Console.WriteLine(shop.Sell());
}
}
//万达商场继承Shop接口
public class WandaShop : Shop
{
public string Sell()
{
return "这里是万达商场";
}
}
//沃尔玛商场继承Shop接口
public class WoermaShop : Shop
{
public string Sell()
{
return "这里是沃尔玛商场";
}
}
public static void Main()
{
Customer customer = new Customer();
customer.Shopping(new WandaShop()); //万达商场
customer.Shopping(new WoermaShop()); //沃尔玛商场
}
}
实现方法:
- 客户端不应该依赖它不需要实现的接口。
- 不建立庞大臃肿的接口,应尽量细化接口,接口中的方法应该尽量少。
- 需要注意的是:接口的粒度也不能太小。如果过小,则会造成接口数量过多,使设计复杂化。
public class Interface_Segregation_Principle //接口隔离
{
public interface RestaurantProtocol //餐厅订单协议
{
void PlaceOnlineOrder(); //网上订餐方式
void PlaceTelePhoneOrder(); //电话订餐方式
void PlaceWalkInCustormerOrder(); //到店订餐方式
void PayInPerson(); //付款方式
}
public class OnlineClient : RestaurantProtocol
{
public void PayInPerson(){ Console.WriteLine("客户网上订餐付款方式"); }
public void PlaceOnlineOrder(){ Console.WriteLine("客户网上订餐"); }
public void PlaceTelePhoneOrder(){ } //无用
public void PlaceWalkInCustormerOrder(){ } //无用
}
public class TelePhoneClient : RestaurantProtocol
{
public void PayInPerson() { Console.WriteLine("客户电话订餐付款方式"); }
public void PlaceOnlineOrder() { } //无用
public void PlaceTelePhoneOrder() { Console.WriteLine("客户电话订餐"); }
public void PlaceWalkInCustormerOrder() { } //无用
}
public class WalkInClient : RestaurantProtocol
{
public void PayInPerson() { Console.WriteLine("客户到店订餐付款方式"); }
public void PlaceOnlineOrder() { } //无用
public void PlaceTelePhoneOrder() { }//无用
public void PlaceWalkInCustormerOrder() { Console.WriteLine("客户到店订餐");}
}
}
public class Interface_Segregation_Principle //接口隔离
{
public interface RestaurantPlaceOrderProtocol //餐厅订单地点协议
{
void PlaceOrder(); //订餐地点
}
public interface RestaurantPaymentProtocol //餐厅订单方式协议
{
void PayOrder();
}
//网上客户
public class OnlineClient : RestaurantPaymentProtocol, RestaurantPlaceOrderProtocol
{
public void PayOrder(){ Console.WriteLine("客户网上订餐付款方式"); }
public void PlaceOrder(){ Console.WriteLine("客户网上订餐"); }
}
//电话客户
public class TelePhoneClient : RestaurantPaymentProtocol, RestaurantPlaceOrderProtocol
{
public void PayOrder() { Console.WriteLine("客户电话订餐付款方式"); }
public void PlaceOrder() { Console.WriteLine("客户电话订餐"); }
}
//到店客户
public class WalkInClient : RestaurantPaymentProtocol, RestaurantPlaceOrderProtocol
{
public void PayOrder() { Console.WriteLine("客户到店订餐付款方式"); }
public void PlaceOrder() { Console.WriteLine("客户到店订餐"); }
}
}
public class Liskov_Sbstitution_Principle //里氏替换原则
{
public static void Main()
{
BrownKiwi brownKiwi = new BrownKiwi();
brownKiwi.FlySpeed = 120;
Console.WriteLine($"几维鸟飞行时间:{brownKiwi.GetFlyTime(300)}");
/*几维鸟飞行时间:∞*/
}
//子类:燕子类
public class Swallow : Birds { }
//子类:几维鸟类
public class BrownKiwi : Birds
{
private int flySpeed;
public int FlySpeed { set { this.flySpeed = 0; } get { return 0; } }
}
//父类:鸟类
public class Birds
{
//飞行速度
public int FlySpeed { set; get; }
//获得飞行时间
public double GetFlyTime(double distance)
{
return (distance / FlySpeed);
}
}
}
public class Person
{
// 使用洗衣机洗衣服的方法
public void washClothes(WashingMachine washingMachine)
{
Console.WriteLine("准备清洗。");
washingMachine.receiveClothes();
washingMachine.wash();
washingMachine.drying();
}
}
public class WashingMachine
{
// 接收衣服的方法
public void receiveClothes()
{
Console.WriteLine("洗衣机接收衣服");
}
// 洗涤的方法
public void wash()
{
Console.WriteLine("洗衣机开始洗衣服");
}
// 烘干的方法
public void drying()
{
Console.WriteLine("洗衣机开始烘干衣服");
}
}
public class WashingMachine
{
// 自动洗衣
public void automatic()
{
this.receiveClothes();
this.wash();
this.drying();
}
// 接收衣服的方法
private void receiveClothes()
{
Console.WriteLine("洗衣机开始接收衣服");
}
// 洗涤的方法
private void wash()
{
Console.WriteLine("洗衣机开始洗衣服");
}
// 烘干的方法
private void drying()
{
Console.WriteLine("洗衣机开始烘干衣服");
}
}
public class Person
{
// 使用洗衣机洗衣服的方法
public void washClothes(WashingMachine washingMachine)
{
Console.WriteLine("准备清洗。");
washingMachine.automatic();
}
}