您能帮助我在实际示例中理解抽象类与接口的用法吗?
-
06-07-2019 - |
题
您能否让我对抽象类与继承的使用有一个近乎过于简单化的理解并帮助我,以便我能够真正理解这个概念以及如何实现?我有一个想要完成的项目,但不知道如何实施。我一直在和我的教授聊天,他经常责备我,说如果我无法弄清楚,我可能还没有准备好学习这门课程。我已经完成了先决条件课程,但仍然难以理解这些概念。
为了澄清这一点,我到目前为止所做的项目如下。我还没有填写狗/猫课程等。能给我指点一下吗?我不是要任何人给我“答案”。我只是迷失在去哪里。我参加在线课程,他与我的沟通努力一直令人不安。我刚刚完成了 4.0 和所有其他课程,所以我愿意付出努力,但我迷失在对这些概念的理解以及如何实际应用它们上。
有什么意见或帮助可以让我在这个项目上取得进一步的进展吗?
我要实现的内容的描述如下:
概述:
本练习的目的是证明界面,继承,抽象类和多态性的使用。您的任务是服用提供的程序外壳,并添加适当的类和相应的类成员/方法,以使该程序正确运行。您可能不会更改所提供的任何代码,您只能添加您编写的类。尽管有很多方法可以使该程序正常工作,但您必须使用证明界面使用的技术,
继承,抽象类和多态性。同样,要清楚地说,您可以添加到提供的代码中,但不能更改或删除任何一个。提供的代码将在几乎没有其他代码的情况下使用,并满足练习的要求。如果您成功完成任务,则您的程序应在运行时输出以下语句:
我的名字是 Spot,我是一只狗
我的名字是菲利克斯,我是一只猫
要求:
1)您必须有一个称为“动物”的抽象基类,狗和猫类从中得出。
2)动物基类必须源自界面“ ianimal”,它是唯一应源自ianimal的类。
3)由于所有动物都有名字,而名称不是狗或猫的特定属性,所以
基类应该是存储名称的位置,以及实现whateSmyname get-property的位置。
4)您将需要创建只能从动物基类派生的狗和猫课。
5)狗和猫类应实现Whatami Get-Property并返回适当的字符串值。
您无法更改的代码:
using System;
namespace IT274_U2
{
public interface IAnimal
{
string WhatAmI { get; }
string WhatIsMyName { get; }
}
public class TesterClass
{
public static void DescribeAnimal(IAnimal animal)
{
Console.WriteLine("My name is {0}, I am a {1}", animal.WhatIsMyName, animal.WhatAmI);
}
static void Main(string[] args)
{
Dog mydog = new Dog("Spot");
Cat mycat = new Cat("Felix");
DescribeAnimal(mydog);
DescribeAnimal(mycat);
}
}
}
///////////////////////
到目前为止我写的代码:
using System;
namespace IT274_U2
{
public interface IAnimal
{
string WhatAmI { get; }
string WhatIsMyName { get; }
}
public class Dog
{
public abstract string WhatAmI
{
get;
set;
}
}//end public class Dog
public class Cat
{
public abstract string WhatIsMyName
{
get;
set;
}
}//end public class Cat
public abstract class Animal : IAnimal
{
// fields
protected string Dog;
protected string Cat;
// implement WhatIsMyName
//properties
public abstract String Dog
{
get;
set;
}
public abstract String Cat
{
get;
set;
}
public abstract string WhatIsMyName();
} //end public abstract class Animal
public class TesterClass
{
public static void DescribeAnimal(IAnimal animal)
{
Console.WriteLine("My name is {0}, I am a {1}", animal.WhatIsMyName, animal.WhatAmI);
}
static void Main(string[] args)
{
Dog mydog = new Dog("Spot");
Cat mycat = new Cat("Felix");
DescribeAnimal(mydog);
DescribeAnimal(mycat);
}
}
}
解决方案
修改强>
我已经为每个课程提取了代码 - 如果你想看到我的答案,请看一下编辑修订:)
首先我们定义界面
public interface IAnimal
{
string WhatAmI { get; }
string WhatIsMyName { get; }
}
任何实现此接口的类都必须实现这些属性。界面就像合同;实现接口的类同意提供接口的方法,属性事件或索引器的实现。
接下来,我们需要定义您的抽象Animal类
public abstract class Animal : IAnimal
{
//Removed for Training, See Edit for the code
}
该课程为摘要表示该类仅用于其他类的基类。我们已经实现了接口的两个属性,并且还有一个私有字段来存储动物名称。另外,我们已经将 WhatAmI
属性访问器抽象化,以便我们可以在每个派生类中实现我们自己的特定属性访问器逻辑,并且还定义了一个接受字符串参数的构造函数并将值赋给 _name
私有字段。
现在,让我们定义 Cat
和 Dog
类
public class Dog : Animal
{
//Removed for Training, See Edit for the code
}
public class Cat : Animal
{
//Removed for Training, See Edit for the code
}
这两个类都继承自 Animal
,每个类都有一个定义字符串参数的构造函数,并将该参数作为参数传递给基础构造函数。此外,每个类都为 WhatAmI
实现了它自己的属性访问器,分别返回了它们类型的字符串。
代码的其余部分
public class Program
{
public static void DescribeAnimal(IAnimal animal)
{
Console.WriteLine("My name is {0}, I am a {1}", animal.WhatIsMyName, animal.WhatAmI);
}
static void Main(string[] args)
{
Dog mydog = new Dog("Spot");
Cat mycat = new Cat("Felix");
DescribeAnimal(mydog);
DescribeAnimal(mycat);
Console.ReadKey();
}
}
静态方法 DescribeAnimal
接受 IAnimal
作为参数,并写出 WhatIsMyName
和 WhatAmI 返回的值代码>传递的
IAnimal
的属性访问器。
由于 Animal
实现 IAnimal
, Dog
和 Cat
继承自 Animal
,任何 Cat
或 Dog
对象都可以作为参数传递给 DescribeAnimal
方法。
我希望我已经清楚地解释了这一点,如果有人觉得我的选择需要收紧,请发表评论,我很乐意编辑我的答案。
其他提示
接口和抽象类之间的主要区别在于,在接口中,您只定义应该是实现此接口的对象的公共方法和属性。抽象类提供了一些基本实现,但是具有一些“间隙”。 - 继承者需要实现的抽象方法。
我不打算为你做功课,而是提示:动物类不应该包含任何特定于狗和猫的东西。
你很亲密,但要比实际需要的更难。
我不想给你答案;)但这里有一些指示。
首先,您要创建3个类和1个接口。但是,我相信你可能缺少的一件事是你需要3种不同类型的对象(从“最少定义”到“最定义”):
1)界面
这是IAnimal - 可以通过任何可以像动物一样的方式实现
2)抽象基类 这是Animal calss - 任何动物应该来自动物,但这些不是直接创造的。如果你假装自己是上帝,你就不会制造动物,你会制造一只狗,猫,松鼠或者FuzzyBunny
3)动物的具体实施 这些是实际的类本身。这就是你创造的。在你的情况下狗或猫。
这里的技巧是你只能创建具体的类,但你可以使用IAnimal或Animal(接口或抽象基类)来操纵和使用任何动物(或者,在接口的情况下,任何行为类似于动物)
一般来说:
接口描述了对象将响应的方法。这是对象承诺履行的契约。
抽象类描述基本功能并将专门的功能交给子类。
因此,基本上,当您希望性质不同的对象响应相同的特定方法时,您可以使用接口。
当您需要某个类的专门版本时,您可以使用抽象类。
假设您想要创建一个系统,其中任何类型的对象都可以通过唯一的 id 来标识,并且您不关心它们所属的类。
你可能有:
动物
运输
电脑小玩意。
任何。
由于它们是不相关的主题,您可以选择实现和接口,比方说:
public interface IIdentifiable
{
public long GetUniqueId();
}
所有想要满足此契约的类都将实现该接口。
public class IPod: IIdentifiable
{
public long GetUniqueId()
{
return this.serialNum + this.otherId;
}
}
public class Cat: IIdentifiable
{
public long GetUniqueId()
{
return this.....
}
}
iPod 和 Cat 两者都具有非常不同的性质,但它们都可以响应将在目录系统中使用的“GetUniqueId()”方法。
那么它可以这样使用:
...
IIdentifiable ipod = new IPod();
IIdentifiable gardfield = new Cat();
store( ipod );
store( gardfield );
....
public void store( IIdentifiable object )
{
long uniqueId = object.GetUniqueId();
// save it to db or whatever.
}
另一方面,您可能有一个抽象类,定义对象可能具有的所有常见行为,并让子类定义专门的版本。
public abstract class Car
{
// Common attributes between different cars
private Tires[] tires; // 4 tires for most of them
private Wheel wheel; // 1 wheel for most of them.
// this may be different depending on the car implementation.
public abstract void move();
}
class ElectricCar: Car
{
public void move()
{
startElectricEngine();
connectBattery();
deploySolarShields();
trasnformEnertyToMovemetInWheels();
}
}
class SteamCar: Car
{
public void move()
{
fillWithWather();
boilWater();
waitForCorrectTemperature();
keepWaiting();
releasePreasure....
}
}
在这里,两种汽车以不同的方式实现“移动”方法,但它们仍然在基类中共享共同的东西。
为了让事情变得更有趣,这两辆车可能还实现了可识别的接口,但通过这样做,它们只是承诺响应 GetUniqueId 方法,而不是汽车的本质。这就是为什么汽车本身可能不会实现该接口。
当然,如果识别可以基于汽车可能具有的公共属性,则GetIdentifyingId可以由基类实现,并且子类将继承该方法。
// 情况1 ...每个子类都实现该接口
public class ElectricCar: Car, IIdentifiable
{
public void move()
{
.....
}
public long GetUniqueId()
{
....
}
}
public class SteamCar: Car, IIdentifiable
{
public void move()
{
.....
}
public long GetUniqueId()
{
....
}
}
情况2,基类实现接口,子类从中受益。
public abstract class Car: IIdentifiable
{
// common attributes here
...
...
...
public abstract void move();
public long GetUniqueId()
{
// compute the tires, wheel, and any other attribute
// and generate an unique id here.
}
}
public class ElectricCar: Car
{
public void move()
{
.....
}
}
public class SteamCar: Car
{
public void move()
{
.....
}
}
我希望这有帮助。
-
界面是合约。您可以在此处描述您将提供的功能,而无需任何实施细节
-
抽象类是一个类,其目的是在子类之间共享实现细节。由于它仅用于代码共享/分解目的,因此无法实例化
-
您的实际类将继承自您的抽象类,并在使用抽象类中共享的代码时实现其特定于类的功能。
醇>
说实话,不管是否是家庭作业问题,这让业内人士不知道这个问题。因此我会回答。
接口抽象实现,抽象类也是如此。没有“vs”因为你也可以创建一个实现接口的抽象类。所以不要以为他们彼此在战争。
因此,当您不希望消费者对实现了解太多时,可以使用EITHER。接口在这项工作上要好一点,因为它没有实现,它只是说明了消费者可以按下它们返回的值的哪些按钮,并发送抽象类可能比这更多的地方(甚至更多!) 。所以如果你只是把这一点放在板上你只需要接口。 Ergo,第二点:
当您想要在接口的两个不同实现之间共享公共代码时,使用抽象类。在这种情况下,两个具体实现都继承自实现接口的抽象类。
简单的例子是IDataStore。 SavingToATextFile数据存储区只是一个实现IDataStore的类。但是,MsSqlDataStore和MySqlDataStore将共享公共代码。它们都将继承自实现IDataStore的抽象类SqlDataStore。
抽象类: 为派生类建立一个基础,它们为所有派生类提供契约。 它强制执行heirarchies
接口:
接口不是类,它是方法的定义。
一个类可以包含多个接口,但只能包含一个抽象类。
我希望有帮助
基本上,接口定义了所有实施者必须提供的“合同”(即一组操作/属性)。在这种情况下,IAnimal接口需要WhatAmI和WhatIsMyName属性。抽象类可以提供某些功能,但也会留下一些必须由其子类实现的操作。
对于您的示例,Animal基类能够提供WhatIsMyName功能,因为“Name”是所有动物的属性。但是,它不能提供'WhatAmI'属性,因为只有特定的子类知道它们是什么类型。
您发布的示例代码的问题在于,Animal类知道它的子类,它不应该具有
另一个建议 - (稍微偏离主题,但仍然相关)
出于学习目的,我建议避免使用自动属性。如果您明确地实施它们,它将帮助您了解正在发生的事情。
例如,而不是:
class SomeClass
{
public string MyProperty
{
get;
set;
}
}
尝试自己实现:
class SomeClass
{
public string MyProperty
{
get
{
return "MyValue"; // Probably a private field
}
set
{
// myField = value; or something like that
}
}
我提到这一点是因为它会在这种特殊情况下帮助你。由于您使用的是自动属性,因此编译器会“填充空白”。对于你,在你的情况下,我认为它阻止你得到一些非常有用的编译器错误。当试图理解这些概念是如何工作的时候,自己做这项工作通常会让事情变得更容易,而不是更难。