Как принудительно переопределить метод в потомке, не имея абстрактного базового класса? - PullRequest
42 голосов
/ 22 января 2010

Заголовок вопроса, кажется, немного сбивает с толку, но я постараюсь прояснить мой вопрос здесь.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
    public abstract class Employee
    {
        private string name;
        private int empid;
        BenefitPackage _BenefitPackage = new BenefitPackage();
        public string Name
         {
             get { return this.name; }
             set { this.name = value; }
            }
        public int EmpId
        {
            get { return this.empid; }
            set
            {
                if (value == 1)
                    return;
                this.empid = value; }
        }
        public Employee(string Name, int EmpId)
        {
            this.Name = Name;
            this.EmpId = EmpId;
        }
        public Employee()
        { }

        public abstract void GiveBonus();

    }

    public class Manager : Employee
    {
        private int noofstockoptions;
        public override void GiveBonus()
        {
            Console.WriteLine("Manger GiveBonus Override");
        }
        public int NoOfStockOptions
        {
            get { return this.noofstockoptions; }
            set { this.noofstockoptions = value; }
        }

        public Manager(string Name,int EmpId, int NoOfStockOptions):base(Name,EmpId)
        {
            this.NoOfStockOptions=NoOfStockOptions;
        }

    }
    public class SalesPerson:Employee
    {
        private int noofsales;
        public int NoOfSales
        {
            get { return this.noofsales; }
            set { this.noofsales = value; }
        }

        public SalesPerson(string Name, int EmpId, int NoOfSales):base(Name,EmpId)
        {
            this.NoOfSales = NoOfSales;
        }
        public override void GiveBonus()
        {
            Console.WriteLine("Hi from salesperson");
        }
    }
    public sealed class PTSalesPerson : SalesPerson
    {
        private int noofhrworked;
        public int NoOfHrWorked
        {
            get { return this.noofhrworked; }
            set { this.noofhrworked = value; }

        }
        public PTSalesPerson(string Name, int EmpId, int NoOfSales,int NoOfHrWorked):base(Name,EmpId,NoOfSales)
        {
            this.NoOfHrWorked = NoOfHrWorked;

        }
        //public new void GiveBonus()
        //{
        //    Console.WriteLine("hi from ptsalesperson");
        //} 
    }

    class BenefitPackage
    {
        public int Bonus;
        public int GiveBonus()
        {
            int i = 200;
            return i;
        }

        private class innerPublic
        {
            public int innerBonus;

        }


    }

    class MainClass
    {
        public static void Main()
        { 
        Manager _Manager=new Manager("Vaibhav",1,50);
        PTSalesPerson _PTSalesPerson = new PTSalesPerson("Shantanu", 1, 4, 6);
        _Manager.GiveBonus();

        Employee _emp;
        //_emp = new Employee("new emp",4);
        //_emp.GiveBonus();
        _PTSalesPerson.GiveBonus();
        ((SalesPerson)_PTSalesPerson).GiveBonus();
        Console.ReadLine();    
        }

    }
}

Пожалуйста, не пытайтесь понять весь код. Я обобщаю его.

  1. Сотрудник - это класс Abstract, у которого есть абстрактный метод GiveBonus
  2. SalesPerson является производным от Employee. SalesPerson должен дать определение абстрактному методу GiveBonus. ( SalesPerson не может быть абстрактным )
  3. PTSalesPerson является производным от SalesPerson.

Теперь мой вопрос: как я могу заставить PTSalesPerson иметь собственную реализацию GiveBonus.

Ответы [ 14 ]

94 голосов
/ 22 января 2010

Я думаю, вы думаете об этом неправильно. Разработчики языка не сказали себе: «что нам действительно нужно, так это пометить метод как , который должен быть переопределен , давайте изобретем эту вещь, называемую abstract ». Они сказали: «Виртуальный метод позволяет нам представить идею, что каждый производный тип этого базового типа должен быть в состоянии сделать этот метод . Но что, если нет разумного кода , который может перейти к версии метода базового класса? Я знаю, давайте изобретем эту вещь, называемую абстрактным методом для этого обстоятельства. "

Вот проблема, которую абстрактные методы были предназначены для решения: у вас есть метод, общий для всех производных классов, но нет разумной реализации базового класса, НЕ «Мне нужен способ заставить мои производные типы предоставлять реализацию». То, что производные типы вынуждены предоставлять реализацию, является следствием решения , но не проблемой , предназначенной для решения в первую очередь.

Язык C # не имеет механизма для решения проблемы «Я должен заставить свой подтип обеспечивать собственную реализацию этого метода», потому что это не проблема, которую, насколько мне известно, разработчики языка когда-либо рассматривали, было бы проблемой для большинство наших клиентов.

Итак, мой вопрос к вам: почему вы хотите это сделать? Конечно, разработчик производного класса должен определить, является ли реализация базового класса корректной для производного класса или нет. Это не ваше дело. И даже если бы у вас был какой-то способ сделать это, что помешало бы разработчику просто сказать

override void M() { base.M(); }

Можете ли вы объяснить, с какой целью вы пытаетесь навязать эту работу разработчикам ваших производных классов? Возможно, есть лучший способ добиться того, чего вы хотите.

Но в более общем плане: я не уверен, что ваша иерархия разумно разработана в первую очередь. Когда я вижу метод GiveBonus для Employee, я предполагаю, что это означает, что «сотрудник может дать бонус», а не «сотрудник может получить бонус». Конечно, менеджер дает бонус, а сотрудник получает бонус. Я думаю, что вы можете заставить иерархию сотрудников делать слишком много.

10 голосов
/ 22 января 2010

Нельзя, если вы не сделаете SalesPerson абстрактным или не измените иерархию.

Как насчет:

                       Employee*
                           ^
                           |
                       SalesPersonBase* (have all the code except GiveBonus)
                        ^           ^
                        |           |
                 SalesPerson      PTSalesPerson

И Employee, и SalesPersonBase теперь помечены как абстрактные.

Однако, если вам требуется, чтобы PTSalesPerson не только наследовал поведение, но и наследовал отношение is-a (PTSalesPerson также является SalesPerson), то у вас нет способа заставить это сделать.

Обратите внимание, что приведенный выше текст действителен, только если вы рассматриваете только проверки во время компиляции. Другими словами, если вы хотите, чтобы компилятор жаловался, если вы не добавили переопределение в класс PTSalesPerson, вы не сможете этого сделать, если не выполните то, что я изложил выше.

Однако ничто не мешает вам использовать отражение для проверки методов во время выполнения и выдавать исключение, если метод в PTSalesPerson явно не переопределен там, однако я бы посчитал это взломом.

7 голосов
/ 22 января 2010

Использовать внедрение зависимостей. Создайте BonusCalculator класс:

public abstract class BonusCalculator
{
   public abstract decimal CalculateBonus(Employee e)
}

В вашем базовом классе:

private BonusCalculator Calculator { get; set; }

public void GiveBonus()
{
   Bonus = Calculator.CalculateBonus(this)
}

В конструкторе вашей реализации:

public SomeKindOfEmployee()
{
    Calculator = new SomeKindOfEmployeeBonusCalculator();
}

Кто-то, реализующий подкласс Person, теперь должен явно предоставить ему экземпляр BonusCalculator (или получить NullReferenceException в методе GiveBonus).

В качестве дополнительного бонуса этот подход позволяет различным подклассам Person совместно использовать метод расчета бонуса, если это уместно.

Редактировать

Конечно, если PTSalesPerson происходит от SalesPerson и его конструктор вызывает базовый конструктор, это тоже не сработает.

6 голосов
/ 22 января 2010

Объявите класс Employee как абстрактный, но предоставьте и реализацию GiveBonus(), которая выдает исключение времени выполнения с сообщением типа "Должен быть реализован подклассами" . Это старая практика Smalltalk ... Не уверен, что это полезно для C #. Я использовал его в коде Java.

5 голосов
/ 06 мая 2015

WA может использовать «Интерфейс», поэтому вы определяете его как IBonusGiver {void GiveBonus (); } Затем вместо абстрактного метода и переопределений вы реализуете во всех ваших классах этот новый интерфейс. например PTSalesPerson: SalesPerson, IBonusGiver, заставляющий новую реализацию в каждом классе.

4 голосов
/ 10 марта 2017

Это очень старая тема, но ответ на этот вопрос может быть полезным. Вы можете заставить производный класс иметь собственную реализацию виртуального метода / свойства.

public class D
{
    public virtual void DoWork(int i)
    {
        // Original implementation.
    }
}

public abstract class E : D
{
    public abstract override void DoWork(int i);
}

public class F : E
{
    public override void DoWork(int i)
    {
        // New implementation.
    }
}

Если виртуальный метод объявлен абстрактным, он все еще виртуален для любого наследование класса от абстрактного класса. Класс, наследующий абстрактный метод не может получить доступ к исходной реализации метод - в предыдущем примере DoWork в классе F не может вызвать DoWork на классе D. Таким образом, абстрактный класс может заставить производные классы предоставить новые реализации методов для виртуальных методов.

2 голосов
/ 15 декабря 2012

Мне кажется, это означает, что ваша реализация действительно состоит из двух частей: частичной реализации, которая идет в базовом классе, и недостающего завершения той реализации, которую вы хотите в подклассе. Пример:

public abstract class Base
{
   public virtual void PartialImplementation()
   {
      // partial implementation
   }
}

public sealed class Sub : Base
{
   public override void PartialImplementation()
   {
      base.PartialImplementation();
      // rest of implementation
   }
}

Вы хотите принудительно переопределить, так как начальная реализация не завершена. Что вы можете сделать, так это разделить вашу реализацию на две части: реализованную частичную часть и недостающую часть ожидающей реализации. Пример:

public abstract class Base
{
   public void PartialImplementation()
   {
      // partial implementation

      RestOfImplementation();
   }

   // protected, since it probably wouldn't make sense to call by itself
   protected abstract void RestOfImplementation();      
}

public sealed class Sub : Base
{
   protected override void RestOfImplementation()
   {
      // rest of implementation
   }
}
1 голос
/ 22 января 2010

Единственный способ сделать эту работу, если вы не можете сделать SalesPerson Abstract, это:

1) в SalesPerson.GiveBonus (...) использовать отражение, чтобы определить, является ли 'this' SalesPerson или производным классом а) если не производный класс, сделать текущий код в SalesPerson.GiveBonus б) в противном случае вызовите GiveBonusDerived. (объявите это как виртуальное и сделайте исключение в SalesPerson исключением.)

Откат здесь, отражение медленное. Нет ошибки времени компиляции, если GiveBonusDerived не объявлен и т. Д.

1 голос
/ 22 января 2010

Если вы не собираетесь создавать экземпляр SalesPerson напрямую, то проще всего сделать абстракцию SalesPerson, и это заставит любые дочерние классы реализовать ее (или самим абстрагироваться).

Если в этом методе есть некоторая логика, общая для всех SalesPeople, то вы можете реализовать GiveBonus в SalesPerson как шаблонный метод . Что вызывает некоторый абстрактный метод, необходимый для любых подклассов:

Какой бы способ вы ни выбрали, единственный способ заставить реализацию в дочернем классе - сделать базовый класс абстрактным.

1 голос
/ 22 января 2010

Вы не можете использовать настройку, которую вы описали. У PTSalesPerson уже будет реализация GiveBonus, поскольку он наследуется от SalesPerson.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...