我们假设我有以下声明:
public class ClassA
{
public string FieldA = "Something first";
}
public class ClassB : ClassA
{
public string FieldB = "Something second";
}
众所周知,ClassB
的对象可以在需要ClassA
的每个地方使用。例如方法
public void DoSthWithObject(ClassA a);
可以像这样调用:
ClassB b = new ClassB();
DoSthWithObject(b);
也可以将继承类型转换为父类型:
ClassA a = (b as ClassA);
所以编译器/框架知道如何“使用”需要ClassB
的{{1}}数据。
现在的问题是:是否有可能以某种方式反过来?我们假设我有一个ClassA
类型的变量,我想开始将其用作{ {1}}但同时保留ClassA
从ClassB
继承并且不实例化新对象的所有字段?据我所知,需要为ClassB
的对象分配扩展内存以容纳ClassA
字段。
简单地说我想做这样的事情:
ClassA
(我知道我可以实现ClassB
并使用ClassA a = new ClassA();
ClassB b = (a as ClassB); // some magic happens here
或提供一些克隆机制,但我想避免这样的解决方案。)
修改
由于答案似乎集中在提出替代解决方案或警告我甚至想要按我的意愿去做,我决定稍微改变我的问题并提出赏金,所以也许我可以得到更好的答案。
首先,我希望这与语言无关。
第二:我真的做明白,不是每个工具都是锤子,我可以(我希望)区分 Jack 和< EM>简。我也不需要关于基本OOP的课程(除非有人能够证明我正在犯一个基本的概念错误,我现在显然不知道)。我想知道的是如果和为什么自动的父到子类转换在逻辑上是不可能的并且如果可能的话(并且从评论中可以看出有的语言可能)然后为什么它是危险/充满缺陷。在这些语言中,它是如何实际技术性地完成的,它“支持”它(或者至少不禁止它)。
但我确实期待真正的编程实例而不仅仅是隐喻。
只是为了说清楚:我不是在寻找关于向下传播先前升级物体的信息。这不是这种情况。
如果我是一个希望在同一个表格列上同时聚合和分组的人 - 请告诉我。我知道我很聪明,应该明白。 :-)我只是不想留下这个问题,感觉OOP有一些基本的东西,我没有抓住。
注意
在David的回答中,我提到浮动,但实际上指的是实数(数学术语)。这是因为我的英语相当不完美。请停止挑选它。 :
谢谢
我要感谢大家的回答。我决定给史蒂夫一笔赏金,但这并不意味着我认为问题已经结束了。我仍在寻找反对自动对象转换的论据。我必须给管理员,我感到有些失望,那些警告我最多的人无法提供与转换相关的明确例子(与投票无关)。
根据我自己的研究,我决定在一个单独的答案中注明一些事情(如果有人发现它们有用)。请随时编辑和扩展列表。
答案 0 :(得分:5)
没有!从类的基本描述来看,ClassA 不是ClassB。您描述的关系不是对称/反身的。可以这样想:每把锤子都是一把工具,但不是每一把锤子都是锤子。如果你需要拧一个螺丝,你需要一个工具,但锤子不能完成这项工作。用这个类比替换上面的类定义,我认为它的关系不清楚为什么关系不起作用。
答案 1 :(得分:1)
我和@DavidW在一起 但有时遗传问题可以通过构图来解决。
请阅读此wikipedia artice
另一个选择是使用界面。
public interface IProvideSomeData
{
string Data { get; }
}
public class ClassA, IProvideSomeData
{
public string FieldA = "Something first";
string IProvideSomeData.Data {
get { return FieldA; }
}
}
public class ClassB : ClassA, IProvideSomeData
{
public string FieldB = "Something second";
string IProvideSomeData.Data {
get { return FieldB; }
}
}
public class ClassC : IProvideSomeData
{
public string FieldC = "Something second";
string IProvideSomeData.Data {
get { return FieldC; }
}
}
public void DoSthWithObject(IProvideSomeData a);
这种方式DoSthWithObject
取决于接口,然后更加可重用。
答案 2 :(得分:1)
这是特定语言的实现细节,而不是面向对象思维的固有限制。我可以想象许多场景需要这种行为 - 也许Shape是一个Rectangle,但我们决定将它改成Square(因为square是一种矩形)。正如您所指出的那样,在Java或C#等经典OO语言中执行此类操作非常痛苦。
语言设计完全取决于权衡,而这个特殊功能并没有削减C#或Java以及许多其他OO语言。为什么......
让我们考虑一下我的Rectangle =&gt;一分钟的方形示例。也许在我的新语言Z ##中,我选择将对象实例内部存储为由它们继承的对象组成。所以我的Rectangle是内存中的Shape实例,+一些属性和方法。然后Square只是一个Rectangle形状实例+一些属性和方法。将矩形更改为方形很容易,因为我不需要转换任何东西 - 只需在实例化Square时提供现成的Rectangle。
C#和Java等语言实际上并没有这样做 - 它们将对象实例存储为“扁平” - 在C#中,Square实例不在内存中包含一个Rectangle实例 - 它只是共享一个Rectangle的接口。因此,在这些语言中实现此功能要慢得多,因为它们实际上必须将整个展平结构复制到新的内存位置。
答案 3 :(得分:1)
我在C#中试试你的问题。我尝试使用泛型语言不可知(没有c#静态扩展方法)(但可以使用Type参数和强制转换进行更改)。
对于像这样的完全自动化系统,应该创建一个新的泛型类,并使用以下逻辑用于所有类属性:如果存在则搜索内部类,否则返回private(也可能需要一些内省)。
我尝试回答的方案是您首次提交的方案:
ClassA a = new ClassA(); ClassB b =(a作为ClassB); //一些魔法 发生在这里
using System;
namespace test
{
/// <summary>
/// Base class A
/// </summary>
public class ClassA
{
private string prop1;
public ClassA InternalClassA { get; set; }
public string Prop1
{
get
{
if (this.InternalClassA == null)
{
return prop1;
}
else
{
return this.InternalClassA.Prop1;
}
}
set
{
if (this.InternalClassA == null)
{
this.prop1 = value;
}
else
{
this.InternalClassA.Prop1 = value;
}
}
}
public T AsClass<T>() where T : ClassA
{
T obj = Activator.CreateInstance<T>();
obj.InternalClassA = this;
return obj;
}
}
}
namespace test
{
/// <summary>
/// Child class B
/// </summary>
public class ClassB : ClassA
{
public string Prop2 { get; set; }
}
}
using System;
namespace test
{
class Program
{
static void Main(string[] args)
{
// Instantiate A
ClassA a = new ClassA();
a.Prop1 = "foo";
Console.WriteLine("Base a.prop1: " + a.Prop1);
// a as ClassB
ClassB b = a.AsClass<ClassB>();
b.Prop2 = "bar";
Console.WriteLine("Base b.prop1: " + b.Prop1);
Console.WriteLine("Base b.prop2: " + b.Prop2);
// Share memory
b.Prop1 = "foo2";
Console.WriteLine("Modified a.prop1: " + a.Prop1);
Console.WriteLine("Modified b.prop1: " + b.Prop1);
Console.Read();
}
}
}
结果:
Base a.prop1: foo
Base b.prop1: foo
Base b.prop2: bar
Modified a.prop1: foo2
Modified b.prop1: foo2
我希望它会给你一些想法来实现你想要的东西。
答案 4 :(得分:0)
这里的问题是你想要实现的与继承关系不大。
事实上,假设你有这个:
class A {
int a;
public A(int a){
this.a = a;
}
public int get(){
return a;
}
public String whoAmI(){
return "I am class A";
}
}
class B extends A {
int b;
public B(int a, int b){
super(a);
this.b = b;
}
public int getA(){
return super.get();
}
public int getB(){
return b;
}
public String whoAmI(){
return "I am class B";
}
}
所以现在你可以做什么休闲:
A a = new A(2); // Create a new instance of A
或强>
B b = new B(3,2); // Create a new instance of B
请注意,创建B的新实例时,您还必须传递用于创建超级A类的参数,就像您同时实例化它们一样。这是继承的意义,构建B你实际上正在构建一个新的A元素加上一些特定于B的扩展特征。
你想做的更像是:
A a = new A(2);
B b = new B(3,a);
让我们看看B类的外观如何:
class B {
A a;
int b;
public B(int b, A a){
this.b = b;
this.a = a;
}
public int getA(){
return a.get();
}
public int getB(){
return b;
}
public String whoAmI(){
return "I am class B";
}
}
在这里你可以看到你可以实现你想要的东西,但在这种情况下,B不是A的孩子,它更像是它的包装,而A只是一个B属性。 您还可以向B添加如下方法:
public A a(){
return a;
}
然后打电话:
b.a.get(); // which would be just like calling b.getA()
看到这与继承没有任何关系,因为后者更多的是“当我创建一个子类时,我实际上也在创建一个父类,因此可以使用任何子类,就好像强>这是父母“......
修改强>
好的我有点想出一个例子,在代码中显示@David W在他的回答中说的话:
class Tool {
int age;
int value;
public Tool(int age, int value){
this.age = age;
this.value = value;
}
public void use(){
this.age++;
this.value--;
}
}
class ScrewDriver extends Tool{
boolean starTip;
int tipJamming = 0;
public ScrewDriver(int age, int value, boolean starTip){
super(age,value);
this.starTip = starTip;
}
public void screw(){
super.use();
this.tipJamming++;
}
}
class Hammer extends Tool{
int weight;
public Hammer(int age, int value, int weight){
super(age,value);
this.weight = weight;
}
public void hammer(){
super.use();
}
}
所以你有一个父类工具和两个孩子ScrewDriver和Hammer。如果您说的话可能,那么您可以写下:
Tool screwDriver = new screwDriver(1,10,true); //simple inheritance polymorphism
// Now if you could cast Tool to Hammer you could write
((Hammer)screwDriver).hammer();
这意味着你实际上使用螺丝刀作为锤子,但这实际上打破了继承背后的所有想法,因为如果你可以使用任何工具,那么在定义不同的工具时需要什么?你可以在Tool类中实现你需要的所有方法,并在需要时根据上下文使用它们:
class Tool{
... attributes and constructor ...
pulic void use(){
this.age++;
this.value--;
}
public void screw(){
use();
this.tipJamming++;
}
public void hammer(){
use();
}
}
然后
Tool screwDriver = new Tool(...);
Tool hammer = new Tool(...);
screwDriver.screw();
hammer.hammer();
答案 5 :(得分:0)
ClassB
在某些情况下需要ClassA
无法提供的构造信息。
扩展你的例子:
class ClassA {
int member;
}
class ClassB : ClassA {
int bs_special_member;
}
有两种方法可以实现你想要达到的目标,这两种方式都是普遍接受的(至少据我所知)。
1 动态广告。我假设你知道,但如果不是它只有在你已经实际已经引用了正确类型的对象的基类型referance时才有效。例如,你这样做了:
ClassA a = new ClassB();
((dynamic_cast)a).bs_special_member;
我不知道这是否与语言无关,但我见过的大多数语言都有类似的实现。
2 构造函数。
如果您的ClassA
实例实际上只是 ClassA
并且不知道bs_special_member
的值应该是什么,那么您必须在构造ClassB
时提供此值。
我会这样做(在c ++中,我认为你可以用几乎任何适当的oo语言来做同样的事情):
class ClassB : ClassA {
int bs_special_member;
ClassB(ClassA base) {
member = base.member;
// Apply default value
bs_special_member = 456;
}
ClassB(ClassA base, int value_for_bs_special_member) {
member = base.member;
bs_special_member = value_for_bs_special_member;
}
};
这将允许你写:
ClassA a;
a.member = 672;
ClassB b(a);
print(b.bs_special_member);
ClassB b2(a, 35);
print(b2.bs_special_member);
在大多数语言中,第一个构造函数将允许转换自动发生,第二个构造函数仅用于convienince。如果你不能允许默认值,那么你做错了。
答案 6 :(得分:0)
我不知道你会怎样在C ++中做这样的事情(你正在使用的是什么?),但你可能想要阅读/谷歌获取更多信息的概念被称为“协方差” “和”逆转。“最近我做了很多Scala,它有一个强大的(虽然有点令人困惑)类型系统,允许更复杂的类型规范,包括接受指定类型的父类的方法参数。
例如,在Scala中,我可以使用类型参数定义一个方法,有点像C ++模板,如下所示:
def myFunction[T <: MyClass]( a:T ):T ={
//this method accepts an instance of any subclass of MyClass as a parameter,
//and returns an object of the same type, whatever that type may be.
return a
}
def myFunction[T >: MyClass]( a:T ):T ={
//this method accepts an instance of any *parent* class of MyClass as a parameter,
//and returns an object of the same type, whatever that type may be.
return a
}
您还可以指定多个类型参数,并指定它们之间的关系。例如,如果我有一个“Dog”对象列表,并且我附加了一个“Cat”对象,我应该返回一个“Animal”对象列表。对于列表,返回类型将始终是参数的相同类型或超类型,但是如果需要,scala还允许您在自己的函数中指定相反的类型。
当然,如果函数在编译时不知道你正在使用什么特定的子类,那么你将不会在该上下文中使用该子类中的方法...但是因为它在scala中工作,所以客户端调用这样的函数时的代码可以检索未指定的窄类型的实例作为返回类型,因此可以在函数中执行操作后继续使用子类的方法。
它也适用于以下情况:例如,如果您在类Cat中尝试覆盖返回动物列表的方法,则可以以允许子类返回的方式参数化您的类型“Cat”类型的列表,不违反父接口的合同。在这种情况下,重写的子类实现可以访问Cat。
的方法答案 7 :(得分:0)
实现此目标的唯一一致方法是让继承的类具有包含基类的构造函数。如果不这样做,最终会出现各种潜在的溢出问题,因为对象的内存打印现在与最初创建的内存不同。
如果不这样做会让你遇到类似问题:
ClassA
{
int A;
}
ClassB : ClassA
{
int B;
}
ClassA a1 = new ClassA();
a1.A = 1;
ClassA a2 = new ClassA();
a2.A = 2;
ClassB b = (ClassB)a;
b.B = 3;
在这种情况下,您可能会遇到a2.A突然接受3 ...
的值的问题答案 8 :(得分:0)
关于我自己研究中可能出现的自动转换缺陷的几点说明。如果我不精确或者说BS,请原谅我。 :
如果我们将A
的自动转换定义为B : A
作为create B and pass A as argument so B may copy all data from A into B
的简写,那么:
A
分配的内存来就地转换对象),则效率低于B
,复制数据和丢弃A
。 如果B
拥有自己的构造函数(或者是否调用超级构造函数),那么在克隆B
时是否以及何时应该调用B
的构造函数并不清楚来自A
。
如果B
和A
行为不同,则某些情况下,C
持有对A
的引用,则A
之后转换为B
)引用对象的行为可能会发生变化 - 令人惊讶的是C
预期一致性。
某些语言(例如C#)允许重新引入继承的成员。这似乎可能会导致逻辑上的不一致(int a
中的A
在new int a
中不 B
。
这种转换可能会从外部代码(而不是内部代码)改变对象的状态,因此它反对封装。
每个对象应该是A
或B
(而不是介于两者之间),因此事实证明转换应该是原子操作。这导致一条规则,即A
转换为B
时,代码不应对其进行操作。我不确定是否有可能实现这种自动锁定。 (这个类似于具有不完整构造对象的竞争条件)。
自动转换虽然对于主要设计为数据包装器(结构,ORM类)并且暴露相当与状态无关的方法的类是个好主意。但这似乎很容易通过反思(用支持它的语言)来实现。