cre*_*nmd 14 oop object subclassing
我是一个以OOP术语思考的相对新手,并且尚未找到关于正确方法的"直觉".作为练习,我试图找出你在不同类型的物体之间创建线的位置,以我桌上的饮料为例.
假设我创建一个对象Drink,有喜欢的属性volume和temperature,以及类似的方法pour()和drink(),我挣扎,看到具体的饮料"类型"进来了.
说我有饮料类型Tea,Coffee或者Juice,我的第一直觉是子类,Drink因为它们有共同的属性和方法.
然后问题变成了两个Tea并且Coffee具有像sugars和milk但是Juice没有的属性,而所有三个都有variant(伯爵灰色,十分之一和橙色).
类似地,Tea并且Coffee有一个addSugar()方法,而这对于一个Juice对象没有任何意义.
这是否意味着超类应该有这些属性和方法,即使所有的子类不需要他们,或者我定义它们的子类,尤其是对像的属性variant,其中每个子类有它有自己的有效值列表?
但后来我最终得到了两个addSugar()方法,分别是子类Tea和Coffee子类.
或者给我,然后最终把所有的属性和方法上超一流的,因为大多数都至少一对夫妇饮料的类型,我不知道什么是子类在所有点之间共享?
我担心我只是想抽象太多,但如果我想添加一个新类型,例如Water- variant仍然或闪闪发光的道路,我不想回到自己的角落.
小智 19
面向对象的设计不是孤立完成的.您需要问的问题是我的特定应用程序需要做什么(如果有的话)与Drink类?答案以及设计因应用而异.在OO失败的第一种方法是尝试构建完美的柏拉图式的等级 - 这种事物只存在于完美的柏拉图式世界中,并且在我们所居住的现实世界中没有用处.
jal*_*alf 17
问题的一部分是现实世界的对象没有整齐地排列在层次结构中.每个对象都有许多不同的父母.一杯果汁肯定是一种饮料,但它也是营养的来源 - 但不是每种饮料都是.一杯水里的营养不多.同样,有许多营养来源不是饮料.但大多数语言只允许你从一个类继承.一杯茶在技术上是能量来源(它很热,它含有热能),但电池也是如此.他们有共同的基础吗?
当然,奖金问题是,如果我愿意,有什么可以阻止我将糖加入果汁?在物理上,这是可能的.但这不是传统的做法.你想建模哪个?
最终,不要试图模仿"世界".改为模拟您的问题.您希望您的应用程序如何处理一杯茶?一杯茶在您的应用中的作用是什么?在你的情况下,许多可能的父母中哪一个有意义?
如果你的应用需要区分"你可以添加糖的饮料"和"你只能消费不饮的饮料",那么你可能会有两种不同的基类.你甚至需要普通的"饮料"课吗?也许,也许不是.酒吧可能不在乎它是一种饮料,它是可以饮用的.它是一种可以出售的产品,在一种情况下,您必须询问客户是否需要糖,而在另一种情况下则不需要.但"喝"基类可能没有必要.
但是对于喝酒的人来说,你可能想要一个带有Consume()方法的"Drink"基类,因为这是它的重要方面.
最后,请记住,您的目标是编写一个正常运行的程序,而不是编写完美的OOP类图.问题不是"我怎样才能在OOP中代表不同类型的饮料",而是"如何使我的程序能够处理不同类型的饮料".答案可能是将它们安排在具有公共Drink基类的类层次结构中.但它也可能完全不同.它可能是"对所有饮料都一样,只需更换名称",在这种情况下,一个课程就足够了.或者它可能只是将饮料视为另一种消费品,或者只是另一种液体,而不是实际建模他们的"可饮用"财产.
如果您正在编写物理模拟器,那么电池和一杯茶应该来自同一个基类,因为与您相关的属性是两者都能够存储能量.而现在果汁和茶突然没有任何共同之处.它们可能都是另一个世界的饮料,但在您的申请中?它们完全不同.(并且请不要挑剔果汁如何含有能量.我已经说了一杯水,但是有人可能会带来聚变能力或其他东西;))
永远不要忘记你的目标.为什么你需要在你的程序中模拟饮料?
Too*_*the 13
sugart问题有两种解决方案.第一个是添加一个像HotDrinks这样的子类,其中包含addSugar和addMilk,如:
Drink
/ \
HotDrink Juice
/ \
Tea Coffee
Run Code Online (Sandbox Code Playgroud)
问题是如果有很多这样的话会变得混乱.
另一种解决方案是添加接口.每个类可以实现零个或多个接口.所以你有ISweetened和ICowPowerEnabled接口.Tea和Coffee都实现了ISweetened和ICowPowerEnabled接口.
为了增加其他答案,接口往往为解耦代码提供更大的灵活性.
您必须注意不要将功能与基类相关联,因为它对所有派生类都是通用的.想想是否可以提取此功能并将其应用于其他不相关的对象 - 您会发现"真实世界"基类中的许多方法实际上可能属于不同的接口.
例如,如果你今天在饮料中"添加糖" ,你可能会决定稍后将它添加到煎饼中.然后你最终会得到你的代码,要求在许多地方喝一杯,使用AddSugar和其他各种不相关的方法 - 它很难重构.
如果你的类知道如何做的话,就可以实现该接口,不管是什么类实际上是.例如:
interface IAcceptSugar
{
void AddSugar();
}
public class Tea : IAcceptSugar { ... }
public class Coffee : IAcceptSugar { ... }
public class Pancake : IAcceptSugar { ... }
public class SugarAddict : IAcceptSugar { ... }
Run Code Online (Sandbox Code Playgroud)
使用接口从实际实现它的对象请求特定功能有助于您创建高度独立的代码.
public void Sweeten(List<IAcceptSugar> items)
{
if (this.MustGetRidOfAllThisSugar)
{
// I want to get rid of my sugar, and
// frankly, I don't care what you guys
// do with it
foreach(IAcceptSugar item in items)
item.AddSugar();
}
}
Run Code Online (Sandbox Code Playgroud)
您的代码也变得更容易测试.界面越简单,您就越容易测试该界面的使用者.
[编辑]
也许我不是很清楚,所以我会澄清:如果AddSugar()为一组派生对象做同样的事情,你当然会在它们的基类中实现它.但在这个例子中,我们说Drink并不总是需要实现IAcceptSugar.
所以我们最终会得到一个共同的类:
public class DrinkWithSugar : Drink, IAcceptSugar { ... }
Run Code Online (Sandbox Code Playgroud)
这将以一堆饮料的方式实现AddSugar().
但是,如果你有一天意识到这不是你方法的最佳位置,那么你就不必改变代码的其他部分,如果它们只是通过接口使用该方法.
道德是:只要您的界面保持不变,您的层次结构可能会发生变化.