【面向对象语言】
面向对象程序设计一、面向对象设计原则1. 单一职责原则 SRP错误写法问题在哪正确写法精讲2. 开闭原则 The Open-Closed Principle OCP错误写法问题正确写法精讲3. 里氏替换原则 Liskov Substitution Principle LSP经典反例最容易懵的地方问题正确设计精讲4. 接口隔离原则 Interface Segregation Principle ISP错误写法问题正确写法精讲5. 依赖倒置原则 Dependency Inversion Principle DIP一句话错误写法高层依赖低层问题正确写法使用超重点精讲你不懂的就在这最终效果6. 迪米特法则最少知道原则Law of Demeter LoD错误写法问题正确写法精讲7. 合成/聚合复用原则 Composite/Aggregate Reuse Principle CARP错误写法问题正确写法精讲最终超级总结最关键二、解答疑惑子类一定要重写父类方法吗1. 可以完全不重写直接用父类的方法2. 父类方法不够用 → 子类重写覆盖3. 父类方法只是“占位”子类必须重写4. 重点什么时候必须重写5. 最容易误解的一句话6. 一句话终极总结什么是组合跟多继承的区别一句话核心区别1. 多继承你的代码就是这个含义特点2. 组合完全不一样含义特点3. 一张表看懂区别4. 最关键的一句为什么设计原则推荐“组合优先”多继承的坑组合的优势5. 终极总结背下来就不会混一、面向对象设计原则7 大面向对象设计原则单一职责原则 SRP开闭原则 The Open-Closed Principle OCP里氏替换原则 LSP接口隔离原则 ISP依赖倒置原则 DIP迪米特法则最少知道原则合成 / 聚合复用原则 CARP1. 单一职责原则 SRP一句话一个类只干一件事只因为一个理由修改。错误写法classUser:def__init__(self,name):self.namename# 职责1保存用户信息defsave_to_db(self):print(f保存{self.name}到数据库)# 职责2发送邮件通知defsend_email(self):print(f给{self.name}发邮件)问题在哪User 类既要管用户数据又要管数据库存储还要管发邮件以后改数据库逻辑、改邮件逻辑都要改 User 类 → 类越来越臃肿、越来越乱。正确写法# 只负责用户数据classUser:def__init__(self,name):self.namename# 只负责数据库存储classUserRepository:defsave(self,user:User):print(f保存{user.name}到数据库)# 只负责发邮件classEmailService:defsend(self,user:User):print(f给{user.name}发邮件)精讲每个类只有一个修改原因改数据库 → 只动 UserRepository改邮件 → 只动 EmailService代码清晰、易维护、易测试2. 开闭原则 The Open-Closed Principle OCP一句话对扩展开放对修改关闭。加新功能加新代码不要改老代码。错误写法defcalculate_area(shape):ifshape[type]circle:return3.14*shape[r]**2elifshape[type]rect:returnshape[w]*shape[h]问题以后加三角形、梯形都要改这个函数改老代码 风险高容易把原有功能改坏。正确写法# 抽象规范所有形状必须实现 areaclassShape:defarea(self):raiseNotImplementedError# 扩展圆形classCircle(Shape):def__init__(self,r):self.rrdefarea(self):return3.14*self.r**2# 扩展矩形classRectangle(Shape):def__init__(self,w,h):self.ww self.hhdefarea(self):returnself.w*self.h使用deftotal_area(shapes):returnsum(s.area()forsinshapes)精讲以后加三角形只新增 Triangle 类不用改 Shape、不用改 total_area老代码完全不动 → 安全、稳定3. 里氏替换原则 Liskov Substitution Principle LSP一句话子类必须能完全替换父类不能破坏父类行为。子类不能“违背父类承诺”。经典反例最容易懵的地方classRectangle:def__init__(self,w,h):self.ww self.hhdefset_width(self,w):self.wwdefset_height(self,h):self.hhdefarea(self):returnself.w*self.h# 正方形继承长方形classSquare(Rectangle):def__init__(self,side):super().__init__(side,side)defset_width(self,w):self.ww self.hw# 强行同步破坏父类行为defset_height(self,h):self.wh self.hh问题父类 Rectangle 承诺宽高可以独立设置Square 子类破坏了这个承诺 →不能替换父类正确设计classShape:defarea(self):passclassRectangle(Shape):...classSquare(Shape):...精讲继承不是为了复用代码是为了行为一致子类必须完全遵守父类约定否则就不要继承改用组合或独立实现4. 接口隔离原则 Interface Segregation Principle ISP一句话接口要小而专不要大而全。不要强迫类实现它用不到的方法。错误写法classAnimal:defeat(self):passdeffly(self):passclassDog(Animal):defeat(self):print(吃)deffly(self):# 狗不会飞但被迫实现raiseException(狗不会飞)问题接口太大实现类被迫实现无用方法代码丑陋、不安全。正确写法# 拆成小接口classEatable:defeat(self):passclassFlyable:deffly(self):pass# 狗只需要吃classDog(Eatable):defeat(self):print(吃)# 鸟既能吃又能飞classBird(Eatable,Flyable):defeat(self):print(吃)deffly(self):print(飞)精讲接口越小越灵活谁需要什么能力就继承什么接口避免无用实现、避免异常5. 依赖倒置原则 Dependency Inversion Principle DIP你刚才没懂的那个我再超细致讲一遍一句话高层业务如用户服务不直接依赖具体数据库MySQL/Mongo而是依赖抽象的数据库规范具体数据库反过来依赖这个抽象 → 依赖关系“倒置”了。错误写法高层依赖低层classMySQLDB:defsave(self,data):print(MySQL保存,data)classUserService:def__init__(self):self.dbMySQLDB()# 写死defadd_user(self,name):self.db.save(name)问题想换成 MongoDB必须打开 UserService 改代码 → 紧耦合、难扩展。正确写法# 1. 抽象规范classDatabase:defsave(self,data):pass# 2. 具体实现都遵守规范classMySQLDB(Database):defsave(self,data):print(MySQL保存,data)classMongoDB(Database):defsave(self,data):print(Mongo保存,data)# 3. 高层只依赖抽象classUserService:def__init__(self,db:Database):# 重点self.dbdbdefadd_user(self,name):self.db.save(name)使用# 用 MySQLserviceUserService(MySQLDB())service.add_user(张三)# 用 MongoDBserviceUserService(MongoDB())service.add_user(李四)超重点精讲你不懂的就在这def__init__(self,db:Database):这句话不是说“必须传 Database 对象”而是你传进来的东西必须符合 Database 的规范有 save 方法MySQLDB、MongoDB 都符合 → 都能传。最终效果UserService 完全不知道 MySQL/Mongo 存在切换数据库一行业务代码都不改这就是“倒置”依赖方向反过来了6. 迪米特法则最少知道原则Law of Demeter LoD一句话只和直接朋友说话不和陌生人说话。减少链式调用降低耦合。错误写法classC:defdo(self):print(做事)classB:def__init__(self):self.cC()classA:def__init__(self):self.bB()aA()a.b.c.do()# 链式调用知道太多细节问题外部代码知道 A 里有 BB 里有 C一旦内部结构变了比如改名 c → cc外部代码全部崩溃。正确写法classC:defdo(self):...classB:def__init__(self):self.cC()defdo(self):self.c.do()classA:def__init__(self):self.bB()defdo(self):self.b.do()aA()a.do()# 只和 A 说话精讲外部只调用a.do()内部怎么实现外部完全不关心低耦合、易修改、安全7. 合成/聚合复用原则 Composite/Aggregate Reuse Principle CARP一句话优先使用组合has-a少用继承is-a。继承耦合太强组合更灵活。错误写法classEngine:defstart(self):print(引擎启动)classCar(Engine):# 汽车“是”一个引擎不合理defrun(self):self.start()问题汽车和引擎是“包含关系”不是“继承关系”继承会把引擎所有方法暴露给汽车混乱正确写法classEngine:defstart(self):print(引擎启动)classCar:def__init__(self):self.engineEngine()# 组合汽车有引擎defrun(self):self.engine.start()print(汽车跑)精讲组合低耦合、灵活想换引擎直接换一个 Engine 子类即可不会污染 Car 类的接口最终超级总结最关键单一职责一个类只干一件事开闭原则加代码不改代码里氏替换子类能替换父类不破坏行为接口隔离接口小而专不强迫实现无用方法依赖倒置业务依赖抽象不依赖具体实现迪米特少知道别人细节低耦合合成复用优先组合少用继承二、解答疑惑子类一定要重写父类方法吗先说结论子类不是一定要重写父类方法完全可以不重写。但要不要重写取决于父类方法有没有提供你想要的行为。1. 可以完全不重写直接用父类的方法父类已经实现好了子类直接继承就能用。classAnimal:defeat(self):print(吃东西)classDog(Animal):pass# 不重写任何方法dogDog()dog.eat()# 直接用父类的正常运行这种情况完全合法、非常常见。2. 父类方法不够用 → 子类重写覆盖子类想做不一样的事就重写。classAnimal:defeat(self):print(吃东西)classBird(Animal):defeat(self):print(鸟吃虫子)# 重写3. 父类方法只是“占位”子类必须重写也就是抽象方法。这种才是必须重写不重写会报错。classAnimal:defeat(self):raiseNotImplementedError(子类必须实现 eat)classDog(Animal):passDog().eat()# 报错只有这种情况才是强制要求重写。4. 重点什么时候必须重写只有两种情况父类是抽象类/接口方法没实现只定规范父类方法行为不符合子类逻辑必须改除此之外都可以不重写。5. 最容易误解的一句话你之前看到我说子类必须完全遵守父类约定这句话的意思不是“子类必须重写父类方法”而是“子类不能破坏父类方法原本的行为约定”举个例子classRectangle:defset_width(self,w):...父类约定设置宽度不会影响高度。子类 Square 重写后改宽度同时改高度 →破坏约定。这才是违反里氏替换。跟你重不重写没关系跟你重写后干了啥有关系。6. 一句话终极总结子类不是必须重写父类方法父类方法能用 → 直接继承不用重写父类方法不对 → 重写父类是抽象规范 → 必须重写里氏替换说的是重写后不能破坏父类行为不是说必须重写什么是组合跟多继承的区别我用最直白、最不绕弯的方式给你讲清楚组合 vs 多继承到底区别在哪、什么时候用哪个。一句话核心区别多继承子类“是”多个父类的合体属于身份叠加组合类“拥有”其他对象属于功能装配1. 多继承你的代码就是这个classEatable:defeat(self):passclassFlyable:deffly(self):passclassBird(Eatable,Flyable):defeat(self):print(吃虫子)deffly(self):print(飞)含义Bird 是一个 Eatable同时也是一个 Flyable它把多个类的行为规范继承到自己身上。特点方法直接变成自己的方法调用bird.eat()、bird.fly()是is-a关系是…的一种2. 组合完全不一样classEatable:defeat(self):print(吃东西)classFlyable:deffly(self):print(飞)classBird:def__init__(self):self.eaterEatable()# 组合self.flyerFlyable()# 组合# 调用方式birdBird()bird.eater.eat()bird.flyer.fly()含义Bird 有 Eatable有 Flyable不是“是”而是“拥有”“包含”。特点方法属于成员对象不是自己的调用要通过成员bird.eater.eat()是has-a关系有…3. 一张表看懂区别维度多继承组合关系is-a是…has-a有…写法class Bird(A, B)class Bird: self.a A(); self.b B()方法归属方法变成子类自己的方法属于成员对象不是自己的耦合度高父类一改子类全受影响低换成员就行不影响自身结构灵活性差继承关系写死极高运行时可换组件典型问题菱形继承、命名冲突、行为混乱无继承问题非常干净4. 最关键的一句为什么设计原则推荐“组合优先”多继承的坑鸟继承了 Flyable鸵鸟也想继承鸟但鸵鸟不会飞继承关系就崩了破坏里氏替换。组合的优势classBird:def__init__(self):self.flyerFlyable()classOstrich:def__init__(self):# 不组合 flyer完美解决pass想有什么功能就装配什么不想有就不装非常灵活。5. 终极总结背下来就不会混多继承 我是 A 也是 B组合 我有 A 也有 B继承强耦合组合低耦合设计原则能组合就不继承能少继承就不多继承如果你愿意我可以给你写一段同一个功能用继承实现 vs 用组合实现对比代码你一跑就彻底懂。