Переопределение методов. Обращение к «затененным» элементам класса — КиберПедия 

Опора деревянной одностоечной и способы укрепление угловых опор: Опоры ВЛ - конструкции, предназначен­ные для поддерживания проводов на необходимой высоте над землей, водой...

Семя – орган полового размножения и расселения растений: наружи у семян имеется плотный покров – кожура...

Переопределение методов. Обращение к «затененным» элементам класса

2021-04-18 69
Переопределение методов. Обращение к «затененным» элементам класса 0.00 из 5.00 0 оценок
Заказать работу

Сейчас в базовом классе Person имеется метод Greeting, возвращающий строку приветствия человека. Этим методом могут пользоваться объекты-студенты, поскольку он унаследован. Однако в реальной жизни молодые люди выполняют свои действия с определенными особенностями. Например, в строке приветствия они могут использовать некоторые «украшения», например вместо «Hi, Peter!” - «Hi, Peter! Ну чё?”

Получается, что базовая реализация метода Greeting нас уже не устраивает и мы переопределим в классе Student метод Greeting точно с таким заголовком (сигнатурой):

public string Greeting(Person p)

{ return Greeting(p) + " Ну чё?"; }

Идея понятна – склеить строку базового приветствия с «украшением». Однако наша программа после запуска и некоторого раздумья выдает ошибку:

Process is terminated due to StackOverflowException.

Дело в том, что сейчас метод Greeting рекурсивно вызывает сам себя и этот процесс бесконечен. Случилось это потому, что новый метод Greeting с той же сигнатурой, что и базовый «заслонил» собой базовый. Это не означает, что базовый метод исчез. Однако для его вызова опять придется использовать ключевое слово base:

public string Greeting(Person p)

{ return base. Greeting(p) + " Ну чё?"; }

Ключевое слово base в данном случае указывает на то, что метод Greeting нужно вызывать из базового класса.

Переопределение методов – очень полезный прием. Но наибольшую пользу он принесет только в форме полиморфного поведения, о чем речь пойдет дальше.

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

class Program

{ static void Main(string[] args)

{ A a = new A(); Console.WriteLine(a.x);

B b = new B(); Console.WriteLine(b.x); Console.WriteLine(b.oldX);

}

}

class A { public int x=45; }

class B: A

{ public bool x=true;

public B() { base.x = 34; }

public int oldX { get { return base.x; } }

}

Многоуровневое наследование

Многоуровневое наследование подразумевает возможность использовать производный класс в качестве базового для определения еще одного производного класса. Например, класс Student может стать основой для создания класса студент-магистр, который должен написать магистерскую работу:

class Magister: Student

{ private string diploma;

public Magister(string name, int year, string diploma)

: base(name, year)

{ this.diploma=diploma; }

  ...

}

Полиморфизм

Использование наследования часто приводит к созданию нескольких производных от данного базового классов. Например, мы можем определить несколько производных классов от класса Student. Важной способностью любого студента является способность сдавать экзамен. Допустим, что студенты младших курсов сдают экзамен путем сдачи серии учебных модулей, в результате чего окончательная оценка накапливается как результат оценок по каждому модулю. Студенты старшекурсники сдают «классический» экзамен – в конце учебного периода. Несмотря на разницу в способах сдачи экзаменов, во многих ситуациях управления студентами не хотелось бы постоянно учитывать описанное различие. Приведем программную реализацию данной ситуации.

class Student

{ private string name;

private int mark;

public Student(string n) { name = n; }

public void PassExam() { mark = 0; }

public string Name { get { return name; } }

public int Mark

  { get { return mark; } set { mark = value; } }

}

class YoungStudent: Student

{ private int[] modMarks;

private static Random r=new Random();

public YoungStudent(string n, int modCount)

 : base(n)

{ modMarks = new int[modCount]; }

private void passModule(int n)

{ modMarks[n] = r.Next(0, 13); }

public void PassExam()

{ double s = 0.0; double d;

for (int i = 0; i < modMarks.Length; i++)

{ passModule(i); s += modMarks[i]; }

d = s / modMarks.Length;

Mark = (int)(Math.Round(d));

}

}

class OldStudent: Student

{ private static Random r=new Random();

public OldStudent(string n): base(n) { }

  public void PassExam()

{ Mark = r.Next(0, 13); }

}

UML-диаграмма трех «студенческих» классов:

Класс Student сам по себе для создания объектов использоваться не будет. Поэтому он содержит сугубо формальную реализацию метода PassExam. Однако другие его методы будут успешно использоваться объектами производных классов без переопределения.

Далее в клиентской части (класс Program) мы решаем следующие задачи:

1. Создание множества студентов. Студенты двух типов помещаются в ArrayList.

2. Все созданные студенты сдают экзамен.

3. Выводится статистика оценок по всем студентам.

class Program

{ private static ArrayList students;

static void MakeStudents()

{ students = new ArrayList();

students.Add(new YoungStudent("Peter",2));

students.Add(new OldStudent("Terry"));

students.Add(new YoungStudent("Frank",2));

students.Add(new OldStudent("Ann"));

}

static void PassExams()

{ foreach (Student student in students)

{ switch (student.GetType().Name)

{ case "YoungStudent":

   { ((YoungStudent)student).PassExam(); break; }

   case "OldStudent":

   { ((OldStudent)student).PassExam(); break; }

}

}

}

static void Report()

{ foreach (Student st in students)

Console.WriteLine("Student {0} has mark {1}",

   st.Name, st.Mark);

}

static void Main(string[] args)

{ MakeStudents(); PassExams(); Report(); }

}

Ключевым моментом этой программы является реализация метода PassExam. Идеальным по простоте был бы следующий его вариант:

static void PassExams()

{ foreach (Student student in students) student.PassExam(); }

К сожалению, в таком случае все студенты получили бы оценку 0, поскольку для всех студентов в этом случае работает метод PassExam из базового класса. Дело в том, что решение о том, какой вариант метода PassExam вызывать принимается на стадии компиляции (это называется ранним связыванием) на основании типа объекта. А в приведенной реализации переменная цикла student описана базовым классом Student.

В результате приходится использовать «тяжелую артиллерию» языка C# - средства для работы с информацией о типах во время выполнения программы (Run Time Type Information – RTTI). Основным средством этой категории является метод GetType, возвращающий информацию о типе объекта, на который ссылается переменная во время выполнения программы. Конструкция student.GetType().Name возвращает строку с именем этого типа. Такое решение не только громоздко, но и не надежно. Представьте, как тяжело обудет поддерживать правильность такого программного кода, если:

1. Будут возникать все новые производные классы студентов.

2. Подобные методы, основанные на switch-анализе вариантов, встречаются во многих местах программы.

Данная проблема является типичной для объектно-ориентированного способа разработки программ и имеет свое решение. Разработчикам языка C# (и других ОО языков) удалось предложить средства, обеспечивающие позднее связывание., которое и известно под названием полиморфизма.

Полиморфная реализация метода PassExam потребует следующих шагов:

1. В базовом классе описать метод с ключевым словом virtual:

class Student

{...

public virtual void PassExam() { mark=0;}

...

}

2. В производных классах описать метод с ключевым словом override:

class YoungStudent: Student

{...

 public override void PassExam() {...}

..

}

class OldStudent: Student

{...

 public override void PassExam() {...}

..

}

Вот и все! Теперь метод PassExam идеально прост:

static void PassExams()

{ foreach (Student st in students) st.PassExam(); }

Теперь решение о том, какой из методов PassExam вызывать откладывается на стадию выполнения программы и зависит не от «статического» типа переменной student, а от ее «динамического» типа, то есть от реального типа объекта, на который ссылается переменная в тот или иной момент выполнения программы.

Термин «полиморфизм» (в буквальном переводе с греческого – многоформенность) означает возможность разнотипных объектов самостоятельно продемонстрировать различие в своем поведении без выяснения типа этих объектов извне.

Метод ToString

Характерным примером полиморфного метода является метод ToString. Он определен в классе Object как virtual. Поэтому в производных классах такой же метод удобно переопределять как override. Это обеспечит однотипное (полиморфное) управление Вашими объектами. Например, в классе Student его реализация может быть следующей:

public override string ToString()

{return String.Format("Student {0} has mark {1}",name,mark);}

Благодаря этому упростится реализация метода Report:

static void Report()

{ foreach (Student st in students) Console.WriteLine(st); }

Дело в том, что метод WriteLine неявно вызывает для каждого выводимого им объекта его метод ToString.


Поделиться с друзьями:

Своеобразие русской архитектуры: Основной материал – дерево – быстрота постройки, но недолговечность и необходимость деления...

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

Состав сооружений: решетки и песколовки: Решетки – это первое устройство в схеме очистных сооружений. Они представляют...

История развития пистолетов-пулеметов: Предпосылкой для возникновения пистолетов-пулеметов послужила давняя тенденция тяготения винтовок...



© cyberpedia.su 2017-2024 - Не является автором материалов. Исключительное право сохранено за автором текста.
Если вы не хотите, чтобы данный материал был у нас на сайте, перейдите по ссылке: Нарушение авторских прав. Мы поможем в написании вашей работы!

0.019 с.