4

次の宣言があるとします。

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したがって、コンパイラ/フレームワークは、必要な場所のデータを「使用」する方法を知っていますClassA

そして今、質問:どうにかして逆にすることは可能ですか? タイプの変数があり、ClassAそれをそのまま使用したいとしますClassBが、同時にClassB継承するすべてのフィールドを保持しClassA、新しいオブジェクトをインスタンス化しないとしますか? ClassAのフィールドに対応するには、 のオブジェクトに割り当てられたメモリを拡張する必要があることを理解していますClassB

簡単に言えば、私は次のようなことをしたいです:

ClassA a = new ClassA();
ClassB b = (a as ClassB); // some magic happens here

(クローン作成メカニズムを実装IConvertibleして使用Convertまたは提供できることはわかっていますが、そのようなソリューションは避けたかったのです。)

編集

回答が代替ソリューションの提案に集中しているように思われるので、私は自分の質問を少し変更して報奨金を提案することにしました。

まず第一に、これを言語にとらわれないようにしたい。

2 番目:すべてのツールがハンマーであるとは限らないことを本当に理解しています。また、 JackJaneを区別できることを願っています。また、基本的な OOP のレッスンも必要ありません (現時点では明らかに気付いていない基本的な概念上の誤りを誰かが実際に証明できない限り)。私が知りたいのは、自動化された親からサブクラスへの変換が論理的に不可能である場合、およびその理由と、それが可能である場合 (そして、コメントから見えるように、可能である言語がある場合)、なぜそれが危険であるか、欠陥に満ちているかです。また、これらの言語で実際に技術的にどのように行われているか、それを「サポート」している (または少なくとも禁止していない) ことも非常に興味深いです。

しかし、私は単なる比喩ではなく、実際のプログラミング例を期待しています。

明確にするために、以前にアップキャストされたオブジェクトのダウンキャストに関する情報を探しているわけではありません。ここではそうではありません。

私が同じテーブル列で同時に集計とグループ化を期待している場合は、これを見せてください。私は理解するのに十分賢いことを知っています。:-) 私は、OOP について私が理解していない基本的な何かがあると感じて、この質問を残したくありません。

知らせ

David's answer の下のコメントで、浮動小数点数について言及していましたが、実際には実数(数学用語) を参照していました。それは私のかなり不完全な英語によるものです。それを拾うのはやめてください。:]

ありがとうございました

皆さんの回答に感謝したいと思います。私はスティーブに報奨金を与えることにしましたが、それは質問が閉じられたと考えているという意味ではありません. 自動化されたオブジェクト変換に対する議論をまだ探しています。私に最も警告していた人たちが、コンバージョンに関連する (そしてキャスティングに関連しない) 明確な例を提供できなかったことに少しがっかりしたことを管理しなければなりません。

私自身の調査から、別の回答でいくつかのことを書き留めることにしました(誰かがそれらを役に立つと思うかもしれません)。リストを自由に編集および拡張してください。

4

9 に答える 9

5

いいえ!クラスの基本的な説明から、ClassA はClassBではありません。あなたが説明する関係は、対称的/再帰的ではありません。次のように考えてください。すべてのハンマーはツールですが、すべてのツールがハンマーであるとは限りません。ネジを回すには工具が必要ですが、ハンマーではダメです。上記のクラス定義をその類推に置き換えると、関係がうまくいかない理由が一見するとかなり明確になると思います。

于 2012-09-01T22:07:24.923 に答える
1

私は@DavidWと一緒です。
しかし、継承の問題は、構成によって解決できる場合があります。

このウィキペディアの記事を読んでください

別のオプションは、インターフェースを使用することです。

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はインターフェイスに依存するため、はるかに再利用可能です。

于 2012-09-01T22:26:40.070 に答える
1

これは特定の言語の実装の詳細であり、オブジェクト指向の考え方に固有の制限ではありません。この動作が望ましい多くのシナリオを想像できます。おそらく、Shape は Rectangle ですが、Square に変更することにします (正方形は長方形の一種であるため)。お気づきのように、Java や C# などの従来の OO 言語でこの種のことを行うのは苦痛です。

言語設計はトレードオフがすべてであり、この特定の機能は、C# や Java などの多くの OO 言語では役に立ちませんでした。理由を確認するには...

Rectangle => Square の例を少し考えてみましょう。おそらく、私の新しい言語 Z## では、オブジェクト インスタンスを、継承元のオブジェクトを合成したものとして内部に格納することを選択します。したがって、私の Rectangle はメモリ内の Shape インスタンス + いくつかのプロパティとメソッドです。次に、Square は単なる Rectangle シェイプ インスタンス + いくつかのプロパティとメソッドです。何も変換する必要がないため、Rectangle を Square に変更するのは簡単です。Square をインスタンス化するときに、既製の Rectangle を提供するだけです。

C# や Java などの言語は実際にはこれを行いません。オブジェクト インスタンスを「フラット化」して格納します。C# では、Square インスタンスはメモリ内に Rectangle インスタンスを含まず、Rectangle のインターフェイスを共有するだけです。したがって、これらの言語でこの機能を実装するには、実際にはフラット化された構造全体を新しいメモリ位置にコピーする必要があるため、はるかに時間がかかります。

于 2012-09-04T17:00:17.463 に答える
1

あなたの問題を C# で試してみます。私はそれを言語にとらわれず(C#静的拡張メソッドなし)、ジェネリックで実行しようとしました(ただし、Typeパラメーターとキャストで変更できます)。

このような完全に自動化されたシステムの場合、新しいジェネリック クラスを作成し、すべてのクラス プロパティに対して次のロジックで使用する必要があります。存在する場合は内部クラスを検索し、存在しない場合はプライベート クラスを返します (イントロスペクションも必要になる場合があります)。

私が答えようとしているシナリオは、あなたが最初に提出したものです:

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

あなたが望むものを達成するためのアイデアを提供してくれることを願っています。

于 2012-09-10T13:57:23.500 に答える
0

私自身の調査からの可能性のある自動変換の欠陥に関するほんの少しのメモ。私が正確でないか、BSを話せない場合は、私を許してください。:]

  1. Aのアトマイズ変換をthenB : Aの省略形として定義すると、次のようになります。create B and pass A as argument so B may copy all data from A into B

    • それは合成糖であり、実際のクローニングプロセスをほとんど制御できません。
    • または(オブジェクトが割り当てられたメモリを拡張することによってインプレースで変換される場合)、インスタンス化、データのコピー、および破棄Aよりも効率が低いことがわかります。BA
  2. 独自のコンストラクターがある場合B(スーパーコンストラクターを呼び出すかどうかに関係なく)、からBクローンを作成するときにコンストラクターを呼び出す必要があるかどうか、またいつ呼び出す必要があるかは明確ではありません。BA

  3. Bとの動作が異なる場合Aがいくつかのシナリオであり、aCがへの参照を保持しているA場合、(Aへの変換後B)参照オブジェクトの動作が変わる可能性があります-驚くべきことに、Cここで期待される一貫性。

  4. 一部の言語(C#など)では、継承されたメンバーを再導入できます。ここでは論理的な不整合が発生する可能性があるようです(int ainAはinではありません )。new int aB

  5. このような変換は、オブジェクトの状態を(内部コードではなく)外部コードから変更する可能性があるため、カプセル化に反対します。

  6. 各オブジェクトはAまたはB(およびその間のどこかにではなく)である必要があるため、変換は不可分操作である必要があります。Aこれは、に変換されている間はコードが動作してはならないというルールにつながりBます。このような自動ロックを実現できるかどうかはわかりません。(これは、オブジェクトが不完全に構築されている競合状態に似ています)。

自動変換は、主にデータラッパー(構造体、ORMクラス)として設計され、状態に関係のないメソッドを公開するクラスには適しています。しかし、これは(それをサポートする言語で)リフレクションによって簡単に達成できるようです。

于 2012-09-10T13:03:23.353 に答える
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 になるという問題が発生する可能性があります...

于 2012-09-10T12:14:53.623 に答える
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();

    }

}

親クラスの Tool と 2 つの子の 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();
于 2012-09-04T08:28:22.267 に答える
0

ClassB場合によっては、ClassA提供されない構築のための情報が必要になります。

例を拡張するには:

class ClassA {
  int member;
}

class ClassB : ClassA {
  int bs_special_member;
}

達成したいことを達成する方法は 2 つありますが、どちらも一般的に受け入れられています (少なくとも私が知る限り)。

1ダイナミックキャスト。私はあなたが気づいていると仮定していますが、そうでない場合は、実際に持っている基本型参照が実際に正しい型のオブジェクトを参照している場合にのみ機能します。たとえば、次のようにしました。

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);

ほとんどの言語では、最初のコンストラクターによって変換が自動的に行われます。2 番目のコンストラクターは利便性のためだけです。デフォルト値を許可できない場合は、何か間違ったことをしています。

于 2012-09-07T15:14:07.613 に答える
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 のメソッドにアクセスできます。

于 2012-09-09T15:08:04.773 に答える