所以我不是在问“如何”,而是在问“何时”和“为什么”。示例代码总是学习的好方法,所以我会很感激。此外,强调设计过程,而不是仅仅给出一个关于何时和为什么的句子。
我主要使用C++,C#和Python进行编程,但我可能会理解大多数语言中的简单示例。
如果有任何术语混淆,请随意编辑我的问题。我不是母语人士,也不确定所有单词的含义。
构建一个基本类。
这部分指南适用于(几乎)任何类。 记住,计划周全就是成功的一半。 我们正在开发哪种类型的类? 它有哪些属性和需要什么方法? 这些都是我们需要考虑的主要问题。
我们在这里正在处理操作系统控件,因此让我们从一个简单的类开始,比如说Button
。现在,我们的按钮有哪些属性呢?显然,它需要在窗口上的一个位置
。此外,我们不希望每个按钮都是完全相同的大小,所以size
是另一个属性。按钮还“需要”一个label
(绘制在按钮上的文本)。这就是你对每个类所做的事情,你设计它,然后编写它。现在我知道我需要哪些属性,所以让我们来构建这个类。
class Button
{
private:
Point m_position;
Size m_size;
std::string m_label;
}
Point
和Size
类,通常我们需要自己构建它们。
进入下一个类。
现在我们已经完成了一个类(Button
),我们可以转到下一个类。
让我们选择 Slider
,这是一个条形控件,例如帮助您上下滚动网页。
让我们像处理按钮一样开始,我们的滑块类需要什么?
它有窗口上的位置(position
)和滑块的size
。此外,它还有最小值和最大值(最小值表示滚动条设置为滑块顶部,最大值表示滚动条设置为底部)。我们还需要当前值,即滚动条目前所在的位置。现在这就足够了,我们可以构建我们的类:
class Slider
{
private:
Point m_position;
Size m_size;
int m_minValue;
int m_maxValue;
int m_currentValue;
}
创建一个基类。
现在我们有了两个类,第一件事情是我们注意到我们刚刚在两个类中定义了Point m_position;
和Size m_size;
属性。这意味着我们有两个具有共同元素的类,而我们刚刚重复编写了相同的代码,如果我们只能编写一次代码并告诉我们的两个类使用该代码而不是重写它,那将是很棒的,对吧?好吧,我们可以做到。
如果我们有两个具有共同属性的相似类,比如Button
和Slider
,那么“通常”(当然也有例外,但初学者不必担心)建议创建一个基类。它们都是我们操作系统上的控件,具有size
和position
。从这里我们得到一个新类,叫做Control
:
class Control
{
private:
Point m_position;
Size m_size;
}
从共同基类继承相似的类。
现在我们有了包含每个控制常见项的Control
类,我们可以让我们的Button
和Slider
从它继承。这将节省我们时间、计算机内存并最终节约时间。以下是我们的新类:
class Control
{
private:
Point m_position;
Size m_size;
}
class Button : public Control
{
private:
std::string m_label
}
class Slider : public Control
{
private:
int m_minValue;
int m_maxValue;
int m_currentValue;
}
现在有些人可能会说,写两次Point m_position; Size m_size;
比写两次: public Control
和创建Control
类要容易得多。
在某些情况下这可能是正确的,但仍然建议不要重复编写相同的代码,尤其是在创建类时。
此外,谁知道我们最终会发现多少个共同的属性。后来我们可能会意识到需要将Control* m_parent
成员添加到Control
类中,该成员指向包含我们控件的窗口(或面板等)。
另一件事是,如果我们后来意识到除了Slider
和Button
之外,我们还需要TextBox
,我们只需通过class TextBox : public Control { ... }
创建一个文本框控件,并仅编写特定于文本框的成员变量,而不是在每个类中反复编写大小、位置和父级。
最后的想法。
基本上,当您有两个具有共同属性或方法的类时,应该创建一个基类。 这是基本规则,但您可以使用自己的大脑,因为可能会有一些例外。
我自己也不是专业的编码人员,但我正在学习,并且我已经像我的教育者教给我的那样教给了你。我希望您(或至少有人)会发现这个答案有用。 即使有些人说Python和其他鸭子类型语言甚至不需要使用继承,他们是错误的。 使用继承将在更大的项目中节省您很多时间和金钱,最终您将感谢自己创建基类。 您的项目的可重用性和管理将变得容易亿万倍。
当你有两个类包含单个类的属性,或者其中一个类依赖于另一个类时,你需要使用继承。例如:
class animal:
#something
class dog(animal):
#something
class cat(animal):
#something
这里有两个类,狗和猫,它们拥有类动物
的属性。在这里,继承发挥了作用。
class parent:
#something
class child(parent):
#something
这取决于编程语言。
例如,在Python中,通常不需要太多的继承,因为您可以将任何对象传递给任何函数,如果对象实现了正确的方法,一切都会很好。
class Dog:
def __init__(self, name):
self.name = name
def sing(self):
return self.name + " barks"
class Cat:
def __init__(self, name):
self.name = name
def sing(self):
return self.name + " meows"
Dog
和Cat
是不相关的类,但您可以将任何一个实例传递给使用name
并调用方法sing
的函数。Animal
),并将这两个类声明为派生类。class Animal:
def __init__(self, name):
self.name = name
class Dog(Animal):
def sing(self):
return self.name + " barks"
class Cat(Animal):
def sing(self):
return self.name + " meows"
C
,不使用类编写程序。现在让我用正确的继承树在 C++ 中编写相同的代码,看看谁能用更少的代码和更快(更轻)的程序。让我们向我们的程序添加50个新元素,看看哪一个更容易改变和/或重用现有的代码。C++ 能够战胜 C,这是有原因的,只是人们无法理解这个原因。 - user2032433通常你会看到使用狗、动物、猫等例子。但让我们来看看一些实际的东西。在面向对象编程中,类是一种构造,用于创建其自身的实例 - 称为类实例、类对象、实例对象或简称对象。一个类定义了组成成员,使其实例具有状态和行为。数据字段成员(成员变量或实例变量)使类实例维护状态。其他类型的成员,特别是方法,使类实例具有行为。类定义了它们的实例的类型。
{'server': 'google.com'}
,但是如果你使用类来实现,你就明确表明需要这些属性一起使用,并且你将使用它们来完成这个特定的任务。fetch(dict_of_settings)
,但是整个功能都绑定到HTTP类
的属性上,没有这些属性就没有意义。class HTTP:
def __init__(self):
self.server = ...
self.port = ...
...
def fetch(self):
connect to self.server on port self.port
...
r1 = HTTP(...)
r2 = HTTP(...)
r1.port = ...
data = r1.fetch()
这不是很好看和易读吗?
这一点很快就能理解...假设你想在项目中实现依赖注入,以使你的应用程序独立于数据库引擎。
因此,你建议每个数据库连接器都应该实现一个接口(由抽象类表示),然后依赖于你的应用程序中的通用方法。比方说,你定义了DatabaseConnectorAbstract
(在Python中不需要实际定义,但是在C++/C#中提出接口时需要)并定义了方法:
class DatabaseConnectorAbstract:
def connect(): raise NotImplementedError( )
def fetch_articles_list(): raise NotImplementedError( )
...
# And build mysql implementation
class DatabaseConnectorMysql(DatabaseConnectorAbstract):
...
# And finally use it in your application
class Application:
def __init__(self,database_connector):
if not isinstanceof(database_connector, DatabaseConnectorAbstract):
raise TypeError()
# And now you can rely that database_connector either implements all
# required methods or raises not implemented exception
Python异常。请看一下那里的层次结构。
ArithmeticError
是 通用的Exception
,在某些情况下可以变得具体到说FloatingPointError
。在处理异常时这非常有用。
您可以在.NET表单上更好地了解此功能,当添加到表单中时,对象必须是Control
的一个实例,但实际上可以是任何其他类型的对象。整个重点是该对象既是DataGridView
又是Control
(并实现了所有方法和属性)。这与抽象类和接口密切相关,许多现实生活的例子都可以是HTML元素:
class HtmlElement: pass # Provides basic escaping
class HtmlInput(HtmlElement): pass # Adds handling for values and types
class HtmlSelect(HtmlInput): pass # Select is input with multiple options
class HtmlContainer(HtmlElement): pass # div,p... can contain unlimited number of HtmlElements
class HtmlForm(HtmlContainer): pass # Handles action, method, onsubmit
我尽可能地简化了它,所以请随意在评论中提问。
由于您主要关注大局而不是类设计的机制,因此您可能希望熟悉面向对象设计的S.O.L.I.D.原则。这不是严格的程序,而是一组规则,以支持您自己的判断和品味。
要点是一个类代表了单一职责(S)。它只做一件事情并且做得很好。它应该代表一个抽象,最好是代表你的应用逻辑的一部分(封装行为和数据以支持该行为)。它也可以是多个相关数据字段的聚合抽象。类是这种封装的单位,并负责维护您抽象的不变量。这就是:五个坚实的建议,它们更多或少与语言无关,并经受住了时间的考验。软件设计很难,这些规则可以让您避免最常见的问题类型,其他一切都需要通过实践来获得。