Автор работы: Пользователь скрыл имя, 24 Ноября 2011 в 18:44, курсовая работа
Несомненно, работа на ЭВМ с использованием ее истинного языка требует довольно-таки высокой квалификации программиста. По мере того как компьютеров становилось все больше и больше, возникла необходимость облегчить общение с ними. Первое, что было сделано, — автоматизировали распределение памяти и ввели понятные человеку обозначения для машинных команд. Например, умножение сумматора на ячейку ОП обозначили MULT (от английского multiply — умножить) и т. п. Получившийся язык общения с ЭВМ назвали языком Ассемблера.
ПРИМЕЧАНИЕ Вопрос, какие члены базового класса наследуются производными классами, решается в С# через модификаторы доступа, применяемые при описании члена. Подробнее об этом см. главу 5, мы же пока будет считать, что производный класс наследует все члены своего базового класса.
Чтобы понять,
когда и как применять
У новичка в ООП, вероятно, появится вопрос: "А нельзя ли здесь обойтись без объектов? Введи в структуру EMPLOYEE член, описывающий тип оплаты, и напиши функцию вроде этой:
Double CalculatePay(EMPLOYEE" pEmployee, int IHoursWorked) {
// Проверяем указатель pEmployee.
if (pEmployee->type == SALARIED) {
// Вычисляем заработок для служащего на окладе. } else if (pEraployee->type == CONTRACTOR)
{
// Вычисляем заработок по контракту. }
else if (pEmployee->type == HOURLY) <
// Вычисляем почасовой заработок. }
else {
// Выполняем иную обработку. }
// Возвращаем значение, полученное от одного из // вышестоящих операторов. }
В этом коде есть две проблемы. Во-первых, успешное выполнение функции тесно связано со структурой EMPLOYEE. Как я уже говорил, подобная связь очень нежелательна, поскольку любое изменение структуры потребует модификации этого кода. Как объектно-ориентированный программист, вы меньше всего захотите "грузить" пользователей вашего класса подробностями, в которых непосвященному разобраться трудно. Это все равно, как если бы производитель автомата по продаже газировки, перед тем как вам набрать стакан воды, потребовал от вас знания работы внутренних механизмов автомата.
Во-вторых,
такой код нельзя задействовать
повторно. Тот, кто понимает, что
наследование способствует повторному
использованию кода, теперь по достоинству
оценит классы и объекты. Так, в нашем
примере достаточно описать в
базовом классе те члены, которые
будут функционировать
class Employee <
public Employee(string firstName, string lastName,
int age, double payRate) {
this.firstName = firstName; this.lastName = lastName; this.age = age; this.payRate = payRate; }
protected string firstName;
protected string lastName;
protected int age;
protected double payRate;
public double CalculatePay(int hoursWorked) {
// Здесь вычисляется зарплата.
return (payRate * (double)hoursWorked); } }
class SalariedEmployee : Employee
{
public string SocialSecurityNumber;
public void CalculatePay (int hoursWorked)
{
// Вычисляем заработок постоянного служащего.
} }
class ContractEmployee : Employee {
public string FederalTaxId;
public void CalculatePay (int hoursWorked)
{
// Вычисляем заработок для контрактника.
}
}
Отметим три важных момента, вытекающих из данного примера.
В базовом классе Employee описана строковая переменная Employeeld,которая наследуется и классом SalariedEmployee, и классом Contract-Employee. Оба производных класса получили эту переменную автоматически как наследники класса Employee.
Каждый из производных классов реализует свою версию CalculatePay. Вы видите, что они оба унаследовали этот интерфейс, и хотя реализация этих функций различна, пользовательский код остался прежним.
Оба производных
класса в дополнение к членам, унаследованным
из базового класса, имеют свои члены:
в классе SalariedEmployee опис
Этот небольшой
пример показывает, как наследование
функциональных возможностей базовых
классов позволяет создать
Что такое "правильное" наследование
Важнейшую
проблему "правильного" наследования
я начну с терминазамещаемость
А вот еще
одно важное правило, которому я советую
следовать при создании собственной
иерархии классов: любой
унаследованный интерфейс
производного класса
не должен требовать
больше и обещать меньше,
чем в базовом классе. Пренебрежение
этим правилом приводит к разрушению существующего
кода. Интерфейс класса — это контракт
между классом и пользователями, применяющими
этот класс. Имея ссылку на производный
класс, программист всегда может обращаться
с ним, как с базовым классом. Это называется восходящим
преобразованием типа (upcasting). В нашем
примере клиент, имея ссылку на объект ContractEmp-loyee, обла
Полиморфизм
По-моему, самое короткое и выразительное определение полиморфизма таково: это функциональная возможность, позволяющая старому коду вызвать новый. Это свойство ООП, пожалуй, наиболее ценно, поскольку дает вам возможность расширять и совершенствовать свою систему, не затрагивая существующий код.
Предположим, вам нужно написать метод, в котором для каждого объекта из набора Employee вызывается метод CakulatePay. Все просто, если зарплата рассчитывается одним способом: вы можете сразу вставить в набор тип нужного объекта. Проблемы начинаются с появлением других форм оплаты. Допустим, у вас уже есть класс Employee, реализующий расчет зарплаты по фиксированному окладу. А что делать, чтобы рассчитать зарплату контрактников — ведь это уже другой способ расчета! В случае с процедурным языком вам пришлось бы переделать функцию, включив в нее новый тип обработки, так как в прежнем коде такой обработки нет. А объектно-ориентированный язык благодаря полиморфизму позволяет делать различную обработку.
В нашем примере надо описать базовый класс Employee, а затем создать производные от него классы для всех форм оплаты (упомянутых выше). Каждый производный класс будет иметь собственную реализацию методаCakulatePay. Здесь и начинается самое интересное. Возьмите указатель на объект, приведите его к типу-предку и вызовите метод этого объекта, а средства языка времени выполнения обеспечат вам, благодаря полиморфизму, вызов той версии этого метода, которая вам требуется. Поясним сказанное на примере.
using System;
class Employee
{
public Employee(string firstName, string lastName, int age, double payRate)
{
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
this.payRate = payRate; }
protected string firstName;
protected string lastName;
protected int age;
protected double payRate;
public virtual double CalculatePay(int hoursWorked)
{
Console.WriteLine("Employee.
} >
class SalariedEmployee : Employee {
public SalariedEmployee(string firstName, string lastName,
int age, double payRate) : base(firstName, lastName, age, payRate) {}
public override double CalculatePay(int hoursWorked) {
Console.WriteLine("
class ContractorEmployee : Employee {
public ContractorEmployee(string firstName, string lastName, int age, double payRate)
: base(firstName, lastName, age, payRate)
<}
public override double CalculatePay(int hoursWorked) {
Console.WriteLineC'
return 42; // произвольное число } }
class HourlyEmployee : Employee {
public HourlyEmployee(string firstName, string lastName, int age, double payRate)
: base(firstName, lastName, age, payRate)
{}
public override double CalculatePay(int hoursWorked) <
Console.WriteLine("Hou rlyEmployee.CalculatePay");
return 42; // произвольное число > }
class PolyApp {
protected Employee[] employees;
protected void LoadEmployeesQ
{
Console.WriteLine("Загрузка информации о сотрудниках...");
// В реальном приложении эти сведения мы // возьмем, наверное, из базы данных, employees = new Employee[3];
employees[0] = new SalariedEmployee ("Amy", "Ariderson", 28, 100);
employees[1] = new ContractorEmployee ("John", "Maffei", 35, 110); employees[2] = new HourlyEmployee ("Lani", "Ota", 2000, 5);
Console. Writel_ine( "\n"); }
protected void CalculatePayO .
{
foreach(Employee emp in employees)
<
emp.CalculatePay(40);
} }
public static void Main()
{
PolyApp app = new PolyAppQ;
app.LoadEmployees(); app. CalculatePayO; } }
В результате компиляции и запуска этого приложения будут получены такие результаты:
c:\>PolyApp
Загрузка информации о сотрудниках...
SalariedEmployee.CalculatePay
ContractorEmployee.
Полиморфизм
имеет минимум два плюса. Во-первых,
он позволяет группировать объекты,
имеющие общий базовый класс,
и последовательно (например, в цикле)
их обрабатывать. В рассмотренном
случае у меня три разных типа объектов (SalariedEmployee,
ContractorEmployee и Hourly-
Второе достоинство
я упоминал в начале этого раздела:
старый код может использовать новый
код. Заметьте: метод PolyApp.Calculate
Pay перебирает в цикле элементы массива
объектов Employee. Поскольку объекты приводятся
неявно к вышестоящему типу Employee, а
реализация полиморфизма во время выполнения
обеспечивает вызов надлежащего метода,
то ничто не мешает нам добавить в систему
другие производные формы оплаты, вставить
их в массив объектов Employee, и весь существующий
код продолжит работу в своем первоначальном
виде!
Компонентное программирование
М.
Безверхов
Препятствия "на подходах к..." Текущее состояние компонентной идеи в программировании оценить непросто. Как и всякое явление большого масштаба оно вряд-ли может быть оценено плоской и линейной системой оценок. Скорее, можно говорить об устойчивых наборах 'движущих сил' и сегментов 'программостроения' в которых, по тем или иным причинам, преимущество имеют определенные течения. Например, можно рассмотреть программирование из компонент со стороны рынков и категорий товаров, на них обретающихся. Можно рассматривать и со стороны групп пользователей, решающих разными программными средствами какую-то задачу одного класса. А можно - со стороны парадигмы программирования, принятой в той или иной специфичной части информатики. Но здесь мы рассмотрим 'движущие силы' со стороны программиста, создающего программы. Ведь именно 'устройство программиста' является самой важным фактором во всех аспектах проблемы. Да и несмещенные оценки получить трудно, если приглядываться к проблеме исключительно профессиональным взглядом, не отдавая себе отчета что именно он вносит самые существенные искажения. И тут первое, что обращает на себя внимание - резкая отличность программирования, как человеческой деятельности от других: видов деятельности. Например, во всех других областях разработка и проектирование отделены от производства сконструированного, а в программировании - совмещены. Как следствие, многие очевидные бы в организации проектных работ явления (хорошо, кстати, изученные человечеством) здесь видны не столь отчётливо, а организация работ - очень сильно отличается от других отраслей. И прямые аналогии из разноотраслевых подходов работают плохо. Другим
таким уникальным обстоятельством
является то, что никакая, сколь-нибудь
сложная, человеческими руками сделанная,
конструкция не делается из одного
материала. Всегда разные её детали выполняются
из материалов разных, что совершенно
естественно предполагает неявное
'компонентное конструирование'. Не бывает
'цельнометаллических Программы
невещественны, являются как бы воплощением
'чистой идеи' что затрудняет программисту
понимание того, что создание программы
есть процесс конструирования Кроме того, и в самой дисциплине программирования существует большой накопленный багаж подходов, парадигм, профессиональных приёмов, которые отнюдь не способствуют 'компонентному восприятию действительности'. Явления эти - чисто философской природы, поэтому 'простой программист-практик' о них задумывается редко, а они сильно определяют мышление этого самого программиста. В качестве примера рассмотрим только один, но очень характерный (на уровне одной из парадигм программирования) приём 'совмещения несовместимого' в его приложении к процессу конструирования программ. Дело в том, что компонентный способ решения проблем - человеческий способ 'от природы'. Психология восприятия человека такова, что он в состоянии оперировать не более, чем 7+2 объектами одного ранга одновременно. На большее не хватает его внимания. Зато, со значительно меньшими усилиями он способен менять план рассмотрения проблемы переходя с более общего уровня на более частный и наоборот. Поэтому
трудность конструирования А вот 'психология
восприятия компьютера' - совсем другая.
Реализовать алгоритм, обрабатывающий
49 однородных объектов значительно
проще, чем их группировать, а потом
обрабатывать группы. Вследствие этого
образуется некое противоречие между
способом мышления и способом реализации
компьютером задуманного Эта проблема действительно существует ещё со времен первого компьютера. Существует и её чисто инженерное решение - программа-транслятор, как инструмент, преобразующий мощности множества 'в котором мыслит человек' в множество 'в котором исполняет команды компьютер'. По сути, человеку предлагается построить высокоуровневую модель на некотором абстрактном языке, которая формальным способом будет трансформирована в язык управляющий исполнительным устройством. В самом этом нет ничего плохого, напротив, данный подход имеет много очень выгодных сторон и давно стал стандартным. Настолько стандартным, что как-то не сразу и видно, что подход-то не является безразмерно масштабируемым! Ведь какой 'степени мелкости' ни были бы машинные команды и какой 'степени крупности' ни были бы конструкции входного языка, на человеческом-то уровне все равно остается 'семь объектов' которыми человек в состоянии оперировать. А, значит, проблема остаётся... 'Языки программирования' её ослабили. Понятие 'подпрограмма' есть не что иное, как формальная спецификация использующая компонентный образ мышления. Если программист не может удержать в области своего внимания весь фрагмент, он должен выделить части фрагмента, оформить их в виде 'подпрограмм' и перейти к рассмотрению той же ситуации на более высоком уровне абстракции. Только вот проблема-то меняет лишь личину - более высокий уровень абстракции в понятии программиста это 'семантически более высокий уровень', в то время как синтаксически уровень абстракций (которым оперирует транслятор) остается всё тем же. И 'вызов подпрограммы' для процессора есть всего-навсего 'переход с запоминанием адреса возврата': Выходит так, что какие бы структурные образования ни выделялись в программе, как бы они ни комбинировались, на выходе транслятора все равно получается однородный монолит кода и данных. Да ещё какой монолит - ни код, ни данные визуально неразличимы, ибо имеют одну и ту же природу! Наличие 'разрабатываемых в отдельном производственном цикле' библиотек подпрограмм нисколько не влияет на эту проблему - для получения работоспособного модуля уже линкер, но должен увидеть всеисходные части собираемого программного модуля. Отсутствие какого-либо объекта на этапе сборки влечет за собой невозможность получить результат, и то, что сделает линкер опять будет монолитом. Конечно, написанное выше немного утрирует проблему: есть библиотеки динамической компоновки, есть модульно-слоевая архитектура операционных систем - но общая тенденция этим обозначена. Парадигма устройства процессора заставляет программиста мыслить в категориях логических абстракций, отчётливо понимая что они - только его логические абстракции. Что 'на выходе всё равно всё вместе будет и будет плоским'. А потому - компонентное проектирование есть как бы частное дело каждой конкретной программы. Сам собой и очень естественно образуется профессиональный взгляд, что всякая программа есть модель не части, но полной вселенной. Если приводить машиностроительные аналогии, то это взгляд, что при разработке нового станка нужно все перепроектировать заново, и что в одном цеху - один станок. А линия из станков - это не группа отдельных станков, а один большой (другой!) станок... И всё же, это только препятствия. Но к чему? Что в таком случае понимать компонентным программированием без машиностроительных аналогий, и, главное, что оно даёт? Давайте определимся точнее... |