Технология

Автор работы: Пользователь скрыл имя, 24 Ноября 2011 в 18:44, курсовая работа

Краткое описание

Несомненно, работа на ЭВМ с использованием ее истинного языка требует довольно-таки высокой квалификации программиста. По мере того как компьютеров становилось все больше и больше, возникла необходимость облегчить общение с ними. Первое, что было сделано, — автоматизировали распределение памяти и ввели понятные человеку обозначения для машинных команд. Например, умножение сумматора на ячейку ОП обозначили MULT (от английского multiply — умножить) и т. п. Получившийся язык общения с ЭВМ назвали языком Ассемблера.

Содержимое работы - 1 файл

курсовая програмирование.docx

— 85.28 Кб (Скачать файл)

ПРИМЕЧАНИЕ Вопрос, какие члены базового класса наследуются производными классами, решается в С# через модификаторы доступа, применяемые при описании члена. Подробнее об этом см. главу 5, мы же пока будет считать, что производный класс наследует все члены своего базового класса.

Чтобы понять, когда и как применять наследование, вернемся к примеру EmployeeApp. Допустим, в компании есть служащие с разными типами оплаты труда: постоянный оклад, почасовая оплата и оплата по договору. Хотя у всех объектов Employee должен быть одинаковый интерфейс, их внутреннее функционирование может различаться. Например, методCalculatePay для служащего на окладе будет работать не так, как для контрактника. Однако для ваших пользователей важно, чтобы интерфейсCalculatePay не зависел от того, как считается зарплата.

У новичка  в ООП, вероятно, появится вопрос: "А  нельзя ли здесь обойтись без объектов? Введи в структуру 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 описана строковая переменная SocialSecurityNumber, а в класс ContractEmployeeвключено описание члена FederalTaxId.

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

Что такое "правильное" наследование

Важнейшую проблему "правильного" наследования я начну с терминазамещаемость (substitutability), взятого у Маршалла Клайна (Marshall Cline) и Грега Ломау (Greg Lomow) (C++ FAQs, Addison-Wesley, 1998). Этот термин означает, что поведение производного класса достигается путем замещения поведения, заимствованного у базового класса. Это одно из важнейших правил, которое вам нужно соблюдать при построении работающей иерархии классов. (Под "работающими" я подразумеваю системы, выдержавшие проверку временем и оправдавшие надежды на повторное использование и расширение кода.)

А вот еще  одно важное правило, которому я советую  следовать при создании собственной  иерархии классов: любой унаследованный интерфейс производного класса не должен требовать больше и обещать меньше, чем в базовом классе. Пренебрежение этим правилом приводит к разрушению существующего кода. Интерфейс класса — это контракт между классом и пользователями, применяющими этот класс. Имея ссылку на производный класс, программист всегда может обращаться с ним, как с базовым классом. Это называется восходящим преобразованием типа (upcasting). В нашем примере клиент, имея ссылку на объект ContractEmp-loyee, обладает и неявной ссылкой на его базовый класс — объект Employee. Поэтому согласно определению объект ContractEmployee всегда должен поддерживать выполнение функций своего базового класса. Заметьте: это правило распространяется только на функциональные возможности базового класса. В производный класс можно добавить и другие функции, которые выполняют и более узкие (или более широкие) задачи, чем унаследованные функции. Поэтому данное правило применяется только к унаследованным членам, поскольку существующий код рассчитан на работу только с этими членами.  

Полиморфизм

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

Предположим, вам нужно написать метод, в котором  для каждого объекта из набора 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.CalculatePay"); return 42; // произвольное число

} >

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("SalariedEmployee.CalculatePay"); return 42; // произвольное число } } .

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'ContractorEmployee.CalculatePay");

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.CalculatePay HourlyEmployee.CalculatePay

Полиморфизм имеет минимум два плюса. Во-первых, он позволяет группировать объекты, имеющие общий базовый класс, и последовательно (например, в цикле) их обрабатывать. В рассмотренном  случае у меня три разных типа объектов (SalariedEmployee, ContractorEmployee и Hourly-Employee), но я вправе считать их все объектами Employee, поскольку они произведены от базового класса Employee. Поэтому их можно поместить в массив, описанный как массив объектов Employee. Во время выполнения вызов метода одного из этих объектов будет преобразован, благодаря полиморфизму, в вызов метода соответствующего производного объекта.

Второе достоинство  я упоминал в начале этого раздела: старый код может использовать новый  код. Заметьте: метод PolyApp.Calculate Pay перебирает в цикле элементы массива объектов Employee. Поскольку объекты приводятся неявно к вышестоящему типу Employee, а реализация полиморфизма во время выполнения обеспечивает вызов надлежащего метода, то ничто не мешает нам добавить в систему другие производные формы оплаты, вставить их в массив объектов Employee, и весь существующий код продолжит работу в своем первоначальном виде! 
 
 
 
 
 

Компонентное  программирование

М. Безверхов

Препятствия "на подходах к..."

    Текущее состояние компонентной идеи в программировании оценить непросто. Как и всякое явление большого масштаба оно вряд-ли может быть оценено плоской и линейной системой оценок. Скорее, можно говорить об устойчивых наборах 'движущих сил' и сегментов 'программостроения' в которых, по тем или иным причинам, преимущество имеют определенные течения. Например, можно рассмотреть программирование из компонент со стороны рынков и категорий товаров, на них обретающихся. Можно рассматривать и со стороны групп пользователей, решающих разными программными средствами какую-то задачу одного класса. А можно - со стороны парадигмы программирования, принятой в той или иной специфичной части информатики. Но здесь мы рассмотрим 'движущие силы' со стороны программиста, создающего программы. Ведь именно 'устройство программиста' является самой важным фактором во всех аспектах проблемы. Да и несмещенные оценки получить трудно, если приглядываться к проблеме исключительно профессиональным взглядом, не отдавая себе отчета что именно он вносит самые существенные искажения.

    И тут  первое, что обращает на себя внимание - резкая отличность программирования, как человеческой деятельности от других: видов деятельности. Например, во всех других областях разработка и проектирование отделены от производства сконструированного, а в программировании - совмещены. Как следствие, многие очевидные бы в организации проектных работ явления (хорошо, кстати, изученные человечеством) здесь видны не столь отчётливо, а организация работ - очень сильно отличается от других отраслей. И прямые аналогии из разноотраслевых подходов работают плохо.

    Другим  таким уникальным обстоятельством  является то, что никакая, сколь-нибудь сложная, человеческими руками сделанная, конструкция не делается из одного материала. Всегда разные её детали выполняются  из материалов разных, что совершенно естественно предполагает неявное 'компонентное конструирование'. Не бывает 'цельнометаллических автомобилей' и 'цельнокирпичных домов': А вот 'компьютерные программы' - изделия как раз совершенно 'из одного материала', что заменяет настоящее компонентное конструирование очень своеобразным подходом, рассматриваемым немного ниже.

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

    Кроме того, и в самой дисциплине программирования существует большой накопленный  багаж подходов, парадигм, профессиональных приёмов, которые отнюдь не способствуют 'компонентному восприятию действительности'. Явления эти - чисто философской  природы, поэтому 'простой программист-практик' о них задумывается редко, а они  сильно определяют мышление этого самого программиста. В качестве примера  рассмотрим только один, но очень характерный (на уровне одной из парадигм программирования) приём 'совмещения несовместимого' в  его приложении к процессу конструирования  программ.

    Дело  в том, что компонентный способ решения  проблем - человеческий способ 'от природы'. Психология восприятия человека такова, что он в состоянии оперировать  не более, чем 7+2 объектами одного ранга одновременно. На большее не хватает его внимания. Зато, со значительно меньшими усилиями он способен менять план рассмотрения проблемы переходя с более общего уровня на более частный и наоборот.

    Поэтому трудность конструирования любой  модели из нерегулярных элементов сильно растёт с увеличением их числа. Сконструировать  модель из 49 объектов одного ранга для  человека - непосильная задача. Ему  значительно проще сконструировать  семь подсистем и одну систему  из подсистем, объединяющую их все.

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

    Эта проблема действительно существует ещё со времен первого компьютера. Существует и её чисто инженерное решение - программа-транслятор, как инструмент, преобразующий мощности множества 'в котором мыслит человек' в множество 'в котором исполняет команды компьютер'. По сути, человеку предлагается построить высокоуровневую модель на некотором абстрактном языке, которая формальным способом будет трансформирована в язык управляющий исполнительным устройством. В самом этом нет ничего плохого, напротив, данный подход имеет много очень выгодных сторон и давно стал стандартным. Настолько стандартным, что как-то не сразу и видно, что подход-то не является безразмерно масштабируемым! Ведь какой 'степени мелкости' ни были бы машинные команды и какой 'степени крупности' ни были бы конструкции входного языка, на человеческом-то уровне все равно остается 'семь объектов' которыми человек в состоянии оперировать. А, значит, проблема остаётся...

    'Языки  программирования' её ослабили. Понятие  'подпрограмма' есть не что иное, как формальная спецификация использующая компонентный образ мышления. Если программист не может удержать в области своего внимания весь фрагмент, он должен выделить части фрагмента, оформить их в виде 'подпрограмм' и перейти к рассмотрению той же ситуации на более высоком уровне абстракции. Только вот проблема-то меняет лишь личину - более высокий уровень абстракции в понятии программиста это 'семантически более высокий уровень', в то время как синтаксически уровень абстракций (которым оперирует транслятор) остается всё тем же. И 'вызов подпрограммы' для процессора есть всего-навсего 'переход с запоминанием адреса возврата': Выходит так, что какие бы структурные образования ни выделялись в программе, как бы они ни комбинировались, на выходе транслятора все равно получается однородный монолит кода и данных. Да ещё какой монолит - ни код, ни данные визуально неразличимы, ибо имеют одну и ту же природу!

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

    Конечно, написанное выше немного утрирует проблему: есть библиотеки динамической компоновки, есть модульно-слоевая архитектура  операционных систем - но общая тенденция  этим обозначена. Парадигма устройства процессора заставляет программиста мыслить в категориях логических абстракций, отчётливо понимая что они - только его логические абстракции. Что 'на выходе всё равно всё вместе будет и будет плоским'. А потому - компонентное проектирование есть как бы частное дело каждой конкретной программы. Сам собой и очень естественно образуется профессиональный взгляд, что всякая программа есть модель не части, но полной вселенной. Если приводить машиностроительные аналогии, то это взгляд, что при разработке нового станка нужно все перепроектировать заново, и что в одном цеху - один станок. А линия из станков - это не группа отдельных станков, а один большой (другой!) станок...

    И всё  же, это только препятствия. Но к  чему? Что в таком случае понимать компонентным программированием без  машиностроительных аналогий, и, главное, что оно даёт? Давайте определимся  точнее...

 

Информация о работе Технология