Ghi đè các trường hoặc thuộc tính trong các lớp con


145

Tôi có một lớp cơ sở trừu tượng và tôi muốn khai báo một trường hoặc một thuộc tính sẽ có một giá trị khác nhau trong mỗi lớp kế thừa từ lớp cha này.

Tôi muốn định nghĩa nó trong lớp cơ sở để tôi có thể tham chiếu nó trong một phương thức lớp cơ sở - ví dụ ghi đè ToString để nói "Đối tượng này thuộc loại / trường thuộc tính ". Tôi đã có ba cách mà tôi có thể thấy khi làm điều này, nhưng tôi đã tự hỏi - cách tốt nhất hoặc được chấp nhận để làm điều này là gì? Newbie câu hỏi, xin lỗi.

Tùy chọn 1:
Sử dụng một Thuộc tính trừu tượng và ghi đè lên các lớp được kế thừa. Lợi ích này từ việc được thi hành (bạn phải ghi đè lên nó) và nó sạch sẽ. Nhưng, cảm thấy hơi sai khi trả về giá trị mã cứng thay vì đóng gói một trường và đó là một vài dòng mã thay vì chỉ. Tôi cũng phải khai báo một cơ thể cho "tập hợp" nhưng điều đó ít quan trọng hơn (và có lẽ có một cách để tránh điều mà tôi không biết).

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

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

Tùy chọn 2
Tôi có thể khai báo trường công khai (hoặc trường được bảo vệ) và ghi đè rõ ràng vào lớp được kế thừa. Ví dụ dưới đây sẽ cho tôi một cảnh báo để sử dụng "mới" và tôi có thể làm điều đó, nhưng nó cảm thấy sai và nó phá vỡ tính đa hình, đó là toàn bộ vấn đề. Có vẻ như không phải là một ý tưởng tốt ...

abstract class Mother
{
    public int MyInt = 0;
}

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

Tùy chọn 3
Tôi có thể sử dụng trường được bảo vệ và đặt giá trị trong hàm tạo. Điều này có vẻ khá gọn gàng nhưng phụ thuộc vào tôi đảm bảo rằng hàm tạo luôn đặt điều này và với nhiều hàm tạo quá tải, luôn có khả năng một số đường dẫn mã sẽ không đặt giá trị.

abstract class Aunt
{
    protected int MyInt;
}

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

Đó là một câu hỏi lý thuyết và tôi đoán câu trả lời phải là tùy chọn 1 vì đây là lựa chọn an toàn duy nhất nhưng tôi chỉ nắm bắt được C # và muốn hỏi người này có nhiều kinh nghiệm hơn.


trừu tượng công khai int MyInt {get; set;} => chuỗi trừu tượng công khai IntentName {get; set;}: D
Navid Golfioushan

Câu trả lời:


136

Trong ba giải pháp chỉ có Phương án 1đa hình .

Các lĩnh vực tự chúng không thể được ghi đè. Đó chính xác là lý do tại sao Tùy chọn 2 trả về cảnh báo từ khóa mới .

Giải pháp cho cảnh báo không phải là nối thêm từ khóa mới, mà là thực hiện Tùy chọn 1.

Nếu bạn cần trường của bạn là đa hình, bạn cần bọc nó trong Thuộc tính.

Tùy chọn 3 là OK nếu bạn không cần hành vi đa hình. Mặc dù vậy, bạn nên nhớ rằng khi trong thời gian chạy, thuộc tính MyInt được truy cập, lớp dẫn xuất không có quyền kiểm soát giá trị được trả về. Bản thân lớp cơ sở có khả năng trả về giá trị này.

Đây là cách triển khai thực sự đa hình của tài sản của bạn, cho phép các lớp dẫn xuất được kiểm soát .

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 */ }
    }
}

153
Ở một khía cạnh khác, tôi thực sự nghĩ rằng Cha nên áp dụng công thức "Y" và Mẹ, theo logic, "X".
Peter - Tái lập lại

4
Điều gì xảy ra nếu tôi muốn cung cấp một triển khai mặc định trong Parent và nó không trừu tượng?
Aaron Franke

@AaronFranke Tạo chữ ký: public virtual int MyInt {get; }
Ted Bigham

@ Peter-ReinstateMonica Đó sẽ là "lập trình di truyền"
devinbost

18

Tùy chọn 2 là không bắt đầu - bạn không thể ghi đè các trường, bạn chỉ có thể ẩn chúng.

Cá nhân, tôi sẽ chọn tùy chọn 1 mỗi lần. Tôi cố gắng giữ các lĩnh vực riêng tư mọi lúc. Đó là nếu bạn thực sự cần phải có khả năng ghi đè lên tài sản, tất nhiên. Một tùy chọn khác là có một thuộc tính chỉ đọc trong lớp cơ sở được đặt từ tham số hàm tạo:

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)
    {
    }
}

Đó có lẽ là cách tiếp cận phù hợp nhất nếu giá trị không thay đổi trong suốt thời gian tồn tại của cá thể.


Chúng tôi có thể nói điều này bây giờ không chính xác dựa trên msdn.microsoft.com/en-us/l
Library / 9fkccyh4.aspx Bài viết msDN

1
@codingbiz: câu trả lời của tôi nói về tài sản ở đâu? Các lĩnh vực và tài sản không giống nhau.
Jon Skeet

@codingbiz: (Câu trả lời của tôi bây giờ nói về các thuộc tính, phải thừa nhận - nhưng không bao giờ nói rằng bạn không thể ghi đè lên chúng. Nó nói - và nói - rằng bạn không thể ghi đè các trường , điều này vẫn đúng.)
Jon Skeet

7

lựa chọn 2 là một ý tưởng tồi Nó sẽ dẫn đến một cái gì đó gọi là bóng; Về cơ bản, bạn có hai thành viên "MyInt" khác nhau, một người mẹ và người kia là con gái. Vấn đề với điều này là các phương thức được thực hiện ở người mẹ sẽ tham chiếu "MyInt" của người mẹ trong khi các phương thức được thực hiện ở con gái sẽ tham chiếu đến "MyInt" của con gái. điều này có thể gây ra một số vấn đề dễ đọc nghiêm trọng và gây nhầm lẫn sau này.

Cá nhân, tôi nghĩ rằng lựa chọn tốt nhất là 3; bởi vì nó cung cấp một giá trị tập trung rõ ràng và có thể được trẻ em tham khảo nội bộ mà không gặp rắc rối trong việc xác định các lĩnh vực của riêng chúng - đó là vấn đề với tùy chọn 1.


6

Bạn có thể làm điều này

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

ảo cho phép bạn có được một tài sản một cơ thể làm một cái gì đó và vẫn cho phép các lớp con ghi đè lên nó.


4

Bạn có thể định nghĩa một cái gì đó như thế này:

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

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

Bằng cách đặt giá trị là chỉ đọc, nó đảm bảo rằng giá trị cho lớp đó không thay đổi trong suốt vòng đời của đối tượng.

Tôi cho rằng câu hỏi tiếp theo là: tại sao bạn cần nó?


Tĩnh là một lựa chọn từ kém vì nó ngụ ý giá trị sau đó được chia sẻ giữa tất cả các phiên bản của lớp, tất nhiên là không phải vậy.
Winston Smith

3

Nếu bạn đang xây dựng một lớp và bạn muốn có một giá trị cơ sở cho thuộc tính, thì hãy sử dụng virtualtừ khóa trong lớp cơ sở. Điều này cho phép bạn tùy ý ghi đè lên tài sản.

Sử dụng ví dụ của bạn ở trên:

//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;
        }
    }
}

Trong một chương trình:

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

0

Tôi sẽ sử dụng tùy chọn 3, nhưng có một phương thức setMyInt trừu tượng mà các lớp con buộc phải thực hiện. Theo cách này, bạn sẽ không gặp phải vấn đề của lớp dẫn xuất mà quên đặt nó trong hàm tạo.

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

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

Nhân tiện, với tùy chọn một, nếu bạn không chỉ định thiết lập; trong thuộc tính lớp cơ sở trừu tượng của bạn, lớp dẫn xuất sẽ không phải thực hiện nó.

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

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

0

Bạn có thể đi với tùy chọn 3 nếu bạn sửa đổi lớp cơ sở trừu tượng của mình để yêu cầu giá trị thuộc tính trong hàm tạo, bạn sẽ không bỏ lỡ bất kỳ đường dẫn nào. Tôi thực sự xem xét tùy chọn này.

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

}

Tất nhiên, sau đó bạn vẫn có tùy chọn đặt trường riêng tư và sau đó, tùy thuộc vào nhu cầu, hiển thị trình nhận tài sản được bảo vệ hoặc công khai.


0

Tôi đã làm điều này ...

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";
        }
    }
}

Bằng cách này bạn vẫn có thể sử dụng các trường.


Tôi cảm thấy như thế này là một giải pháp đặc biệt cho tạo mẫu hoặc demo.
Zimano

0

Việc thực hiện ví dụ khi bạn muốn có một lớp trừu tượng với việc thực hiện. Các lớp con phải:

  1. Tham số hóa việc thực hiện một lớp trừu tượng.
  2. Kế thừa hoàn toàn việc thực hiện lớp trừu tượng;
  3. Có thực hiện của riêng bạn.

Trong trường hợp này, các thuộc tính cần thiết cho việc thực hiện không nên có sẵn để sử dụng ngoại trừ lớp trừu tượng và lớp con của chính nó.

    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
    }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.