การแทนที่ฟิลด์หรือคุณสมบัติในคลาสย่อย


145

ฉันมีคลาสฐานนามธรรมและฉันต้องการประกาศเขตข้อมูลหรือคุณสมบัติที่จะมีค่าแตกต่างกันในแต่ละคลาสที่สืบทอดจากคลาสพาเรนต์นี้

ฉันต้องการกำหนดใน baseclass เพื่อให้ฉันสามารถอ้างอิงได้ในวิธีการเรียนฐาน - ตัวอย่างเช่นการเอาชนะ ToString จะพูดว่า "วัตถุนี้เป็นประเภททรัพย์สิน / เขต " ฉันมีสามวิธีที่ฉันสามารถเห็นการทำเช่นนี้ แต่ฉันสงสัยว่า - วิธีที่ดีที่สุดหรือยอมรับในการทำเช่นนี้คืออะไร? คำถามมือใหม่ขออภัย

ตัวเลือกที่ 1:
ใช้คุณสมบัตินามธรรมและแทนที่บนคลาสที่สืบทอด ประโยชน์ที่ได้รับจากการบังคับใช้ (คุณต้องลบล้าง) และสะอาด แต่มันรู้สึกผิดเล็กน้อยที่จะส่งคืนค่ารหัสฮาร์ดโค้ดแทนการห่อหุ้มฟิลด์และเป็นโค้ดสองสามบรรทัดแทนที่จะเป็นเพียงฟิลด์ ฉันยังต้องประกาศร่างสำหรับ "ชุด" แต่มันมีความสำคัญน้อยกว่า (และอาจมีวิธีที่จะหลีกเลี่ยงสิ่งที่ฉันไม่ทราบ)

abstract class Father
{
    abstract public int MyInt { get; set;}
}

class Son : Father
{
    public override int MyInt
    {
        get { return 1; }
        set { }
    }
}

ตัวเลือก 2
ฉันสามารถประกาศเขตข้อมูลสาธารณะ (หรือเขตข้อมูลที่มีการป้องกัน) และแทนที่มันอย่างชัดเจนในชั้นเรียนที่สืบทอด ตัวอย่างด้านล่างจะให้คำเตือนแก่ฉันในการใช้ "ใหม่" และฉันอาจทำเช่นนั้นได้ แต่มันรู้สึกผิดและทำลายความแตกต่างซึ่งเป็นประเด็นทั้งหมด ดูเหมือนจะไม่ใช่ความคิดที่ดี ...

abstract class Mother
{
    public int MyInt = 0;
}

class Daughter : Mother
{
    public int MyInt = 1;
}

ตัวเลือก 3
ฉันสามารถใช้เขตข้อมูลที่มีการป้องกันและตั้งค่าในตัวสร้าง ดูเหมือนว่าค่อนข้างจะเป็นระเบียบ แต่ขึ้นอยู่กับฉันที่จะตรวจสอบให้แน่ใจว่า Constructor ตั้งค่านี้เสมอและมี Constructor ที่มีการโอเวอร์โหลดจำนวนมากซึ่งมีโอกาสที่เส้นทางของโค้ดบางอันจะไม่ตั้งค่า

abstract class Aunt
{
    protected int MyInt;
}

class Niece : Aunt
{
    public Niece()
    {
        MyInt = 1;
    }
}

มันเป็นคำถามเชิงทฤษฎีและฉันเดาว่าคำตอบต้องเป็นตัวเลือกที่ 1 เนื่องจากเป็นตัวเลือกที่ปลอดภัยเพียงอย่างเดียวแต่ฉันเพิ่งจะได้สัมผัสกับ C # และต้องการถามผู้คนที่มีประสบการณ์มากกว่านี้


นามธรรมสาธารณะ int MyInt {รับ; set;} => สตริงนามธรรมสาธารณะ IntentName {get; set;}: D
Navid Golforoushan

คำตอบ:


136

ของสามโซลูชั่นเพียงตัวเลือกที่ 1คือpolymorphic

ไม่สามารถแทนที่ฟิลด์ด้วยตนเอง นี่คือเหตุผลที่ตัวเลือกที่ 2ส่งคืน คำเตือนคำหลักใหม่

ทางออกสำหรับการเตือนไม่ใช่เพื่อผนวกคำสำคัญ“ ใหม่” แต่ใช้ตัวเลือกที่ 1

ถ้าคุณต้องการให้เขตข้อมูลของคุณเป็น polymorphic คุณจำเป็นต้องล้อมในคุณสมบัติ

ตัวเลือกที่ 3ก็โอเคถ้าคุณไม่ต้องการพฤติกรรม polymorphic คุณควรจำไว้ว่าเมื่อเข้าถึงรันไทม์ MyInt ของคุณสมบัตินั้นคลาสที่ได้รับจะไม่มีการควบคุมค่าที่ส่งคืน คลาสพื้นฐานด้วยตัวเองสามารถคืนค่านี้

นี่คือลักษณะการนำคุณสมบัติของคุณไปใช้อย่างหลากหลายซึ่งทำให้ชั้นเรียนที่ได้รับสามารถควบคุมได้

abstract class Parent
{
    abstract public int MyInt { get; }
}

class Father : Parent
{
    public override int MyInt
    {
        get { /* Apply formula "X" and return a value */ }
    }
}

class Mother : Parent
{
    public override int MyInt
    {
        get { /* Apply formula "Y" and return a value */ }
    }
}

154
นอกจากนี้ฉันคิดว่าพ่อควรใช้สูตร "Y" และแม่อย่างมีเหตุผล "X"
Peter - Reinstate Monica

4
จะเป็นอย่างไรถ้าฉันต้องการจัดหาการใช้งานเริ่มต้นใน Parent และไม่เป็นนามธรรม
Aaron Franke

@AaronFranke สร้างลายเซ็น: publicIn int เสมือน MyInt {รับ; }
Ted Bigham

@ Peter-ReinstateMonica นั่นจะเป็น "การเขียนโปรแกรมทางพันธุกรรม"
devinbost

18

ตัวเลือกที่ 2 ไม่ใช่แบบเริ่มต้น - คุณไม่สามารถแทนที่ฟิลด์ได้คุณสามารถซ่อนได้เท่านั้น

โดยส่วนตัวฉันจะไปเลือก 1 ทุกครั้ง ฉันพยายามที่จะทำให้ฟิลด์เป็นส่วนตัวตลอดเวลา นั่นคือถ้าคุณจำเป็นต้องสามารถแทนที่คุณสมบัติทั้งหมดแน่นอน ตัวเลือกอื่นคือการมีคุณสมบัติแบบอ่านอย่างเดียวในคลาสฐานซึ่งตั้งค่าจากพารามิเตอร์ตัวสร้าง:

abstract class Mother
{
    private readonly int myInt;
    public int MyInt { get { return myInt; } }

    protected Mother(int myInt)
    {
        this.myInt = myInt;
    }
}

class Daughter : Mother
{
    public Daughter() : base(1)
    {
    }
}

นั่นอาจเป็นวิธีที่เหมาะสมที่สุดหากค่าไม่เปลี่ยนแปลงตลอดอายุการใช้งานของอินสแตนซ์


เราสามารถพูดได้ว่าตอนนี้ไม่ถูกต้องตามmsdn.microsoft.com/en-us/library/9fkccyh4.aspxบทความmsdnนี้แสดงว่าคุณสามารถแทนที่คุณสมบัติได้
codingbiz

1
@ การเข้ารหัสบิซ: คำตอบของฉันพูดถึงคุณสมบัติได้ที่ไหน ฟิลด์และคุณสมบัติไม่เหมือนกัน
Jon Skeet

@ เข้ารหัส: (คำตอบของฉันตอนนี้พูดถึงคุณสมบัติเป็นที่ยอมรับ - แต่มันไม่เคยบอกว่าคุณไม่สามารถแทนที่พวกเขามันพูด - และบอกว่า - คุณไม่สามารถแทนที่เขตข้อมูลซึ่งยังคงถูกต้อง)
Jon Skeet

7

ตัวเลือก 2 เป็นความคิดที่ไม่ดี มันจะส่งผลให้บางสิ่งที่เรียกว่าเงา โดยพื้นฐานแล้วคุณมีสมาชิก "MyInt" ที่แตกต่างกันสองคนหนึ่งในแม่และอีกคนในลูกสาว ปัญหาเกี่ยวกับสิ่งนี้คือวิธีการที่นำมาใช้ในแม่จะอ้างอิงถึง "MyInt" ของแม่ในขณะที่วิธีการที่ดำเนินการในลูกสาวจะอ้างอิงถึง "MyInt" ของลูกสาว สิ่งนี้อาจทำให้เกิดปัญหาการอ่านอย่างรุนแรงและเกิดความสับสนในภายหลัง

โดยส่วนตัวฉันคิดว่าตัวเลือกที่ดีที่สุดคือ 3; เพราะให้ค่ารวมศูนย์ที่ชัดเจนและสามารถอ้างอิงภายในโดยเด็ก ๆ โดยไม่ต้องวุ่นวายกับการกำหนดเขตข้อมูลของตนเอง - ซึ่งเป็นปัญหาของตัวเลือก 1


6

คุณสามารถทำได้

class x
{
    private int _myInt;
    public virtual int myInt { get { return _myInt; } set { _myInt = value; } }
}

class y : x
{
    private int _myYInt;
    public override int myInt { get { return _myYInt; } set { _myYInt = value; } }
}

เสมือนช่วยให้คุณได้รับทรัพย์สินที่ทำอะไรบางอย่างและยังช่วยให้ sub-classes แทนที่มัน


4

คุณสามารถกำหนดสิ่งนี้:

abstract class Father
{
    //Do you need it public?
    protected readonly int MyInt;
}

class Son : Father
{
    public Son()
    {
        MyInt = 1;
    }
}

โดยการตั้งค่าเป็นแบบอ่านอย่างเดียวจะช่วยให้มั่นใจได้ว่าค่าสำหรับคลาสนั้นยังคงไม่เปลี่ยนแปลงตลอดอายุการใช้งานของวัตถุ

ฉันคิดว่าคำถามต่อไปคือ: คุณต้องการมันทำไม


สแตติกเป็นตัวเลือกคำที่ไม่ดีเนื่องจากมันบอกเป็นนัยว่ามีการแบ่งปันค่าระหว่างคลาสทั้งหมดของคลาสซึ่งแน่นอนว่ามันไม่ใช่
Winston Smith

3

หากคุณกำลังสร้างคลาสและคุณต้องการให้มีค่าฐานสำหรับคุณสมบัติจากนั้นใช้virtualคีย์เวิร์ดในคลาสฐาน สิ่งนี้อนุญาตให้คุณเลือกแทนที่คุณสมบัติ

ใช้ตัวอย่างของคุณด้านบน:

//you may want to also use interfaces.
interface IFather
{
    int MyInt { get; set; }
}


public class Father : IFather
{
    //defaulting the value of this property to 1
    private int myInt = 1;

    public virtual int MyInt
    {
        get { return myInt; }
        set { myInt = value; }
    }
}

public class Son : Father
{
    public override int MyInt
    {
        get {

            //demonstrating that you can access base.properties
            //this will return 1 from the base class
            int baseInt = base.MyInt;

            //add 1 and return new value
            return baseInt + 1;
        }
        set
        {
            //sets the value of the property
            base.MyInt = value;
        }
    }
}

ในโปรแกรม:

Son son = new Son();
//son.MyInt will equal 2

0

ฉันจะไปกับตัวเลือก 3 แต่มีวิธีนามธรรม setMyInt ที่ subclasses ถูกบังคับให้ใช้ วิธีนี้คุณจะไม่ได้รับปัญหาของคลาสที่ได้มาโดยลืมที่จะตั้งไว้ใน Constructor

abstract class Base 
{
 protected int myInt;
 protected abstract void setMyInt();
}

class Derived : Base 
{
 override protected void setMyInt()
 {
   myInt = 3;
 }
}

อย่างไรก็ตามด้วยตัวเลือกที่หนึ่งถ้าคุณไม่ได้ระบุ set; ในคุณสมบัติคลาสฐานนามธรรมของคุณคลาสที่ได้รับไม่จำเป็นต้องใช้มัน

abstract class Father
{
    abstract public int MyInt { get; }
}

class Son : Father
{
    public override int MyInt
    {
        get { return 1; }
    }
}

0

คุณสามารถไปกับตัวเลือก 3 หากคุณปรับเปลี่ยนคลาสฐานนามธรรมของคุณเพื่อต้องการค่าคุณสมบัติในนวกรรมิกคุณจะไม่พลาดเส้นทางใด ๆ ฉันจะพิจารณาตัวเลือกนี้จริงๆ

abstract class Aunt
{
    protected int MyInt;
    protected Aunt(int myInt)
    {
        MyInt = myInt;
    }

}

แน่นอนว่าคุณยังคงมีตัวเลือกในการทำให้ฟิลด์เป็นแบบส่วนตัวและจากนั้นขึ้นอยู่กับความต้องการการเปิดเผยตัวรับทรัพย์สินที่ได้รับการป้องกันหรือสาธารณะ


0

ฉันทำอย่างนี้...

namespace Core.Text.Menus
{
    public abstract class AbstractBaseClass
    {
        public string SELECT_MODEL;
        public string BROWSE_RECORDS;
        public string SETUP;
    }
}

namespace Core.Text.Menus
{
    public class English : AbstractBaseClass
    {
        public English()
        {
            base.SELECT_MODEL = "Select Model";
            base.BROWSE_RECORDS = "Browse Measurements";
            base.SETUP = "Setup Instrument";
        }
    }
}

วิธีนี้คุณยังสามารถใช้เขตข้อมูลได้


ฉันรู้สึกว่านี่เป็นวิธีที่ดีสำหรับการสร้างต้นแบบหรือการสาธิต
Zimano

0

ตัวอย่างการนำไปใช้เมื่อคุณต้องการให้คลาสนามธรรมพร้อมการนำไปใช้งาน คลาสย่อยต้อง:

  1. พารามิเตอร์การใช้งานของระดับนามธรรม
  2. สืบทอดการใช้คลาสนามธรรมอย่างเต็มที่
  3. มีการใช้งานของคุณเอง

ในกรณีนี้คุณสมบัติที่จำเป็นสำหรับการนำไปใช้ไม่ควรพร้อมใช้งานยกเว้นคลาสนามธรรมและคลาสย่อยของตัวเอง

    internal abstract class AbstractClass
    {
        //Properties for parameterization from concrete class
        protected abstract string Param1 { get; }
        protected abstract string Param2 { get; }

        //Internal fields need for manage state of object
        private string var1;
        private string var2;

        internal AbstractClass(string _var1, string _var2)
        {
            this.var1 = _var1;
            this.var2 = _var2;
        }

        internal void CalcResult()
        {
            //The result calculation uses Param1, Param2, var1, var2;
        }
    }

    internal class ConcreteClassFirst : AbstractClass
    {
        private string param1;
        private string param2;
        protected override string Param1 { get { return param1; } }
        protected override string Param2 { get { return param2; } }

        public ConcreteClassFirst(string _var1, string _var2) : base(_var1, _var2) { }

        internal void CalcParams()
        {
            //The calculation param1 and param2
        }
    }

    internal class ConcreteClassSecond : AbstractClass
    {
        private string param1;
        private string param2;

        protected override string Param1 { get { return param1; } }

        protected override string Param2 { get { return param2; } }

        public ConcreteClassSecond(string _var1, string _var2) : base(_var1, _var2) { }

        internal void CalcParams()
        {
            //The calculation param1 and param2
        }
    }

    static void Main(string[] args)
    {
        string var1_1 = "val1_1";
        string var1_2 = "val1_2";

        ConcreteClassFirst concreteClassFirst = new ConcreteClassFirst(var1_1, var1_2);
        concreteClassFirst.CalcParams();
        concreteClassFirst.CalcResult();

        string var2_1 = "val2_1";
        string var2_2 = "val2_2";

        ConcreteClassSecond concreteClassSecond = new ConcreteClassSecond(var2_1, var2_2);
        concreteClassSecond.CalcParams();
        concreteClassSecond.CalcResult();

        //Param1 and Param2 are not visible in main method
    }
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.