Тема 8. Модели решения функциональных и вычислительных задач

advertisement
МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ
INTERNATIONAL BANKING INSTITUTE
Тема 8. Модели решения функциональных и
вычислительных задач
Цель
Изучить объектно-ориентированные концепции наследования и
полиморфизма и принципы определения качества программ.
Задачи
1. Познакомиться с синтаксисом методов в VB .NET.
2. Рассмотреть базовый принцип объектно-ориентированного
проектирования – перегрузку методов класса.
3. Познакомиться с разделяемыми (Shared) методами.
4. Научиться разрабатывать иерархию наследования классов.
5. Освоить принцип полиморфизма (переопределения).
6. Научиться создавать абстрактные классы и абстрактные
методы.
7. Рассмотреть критерии качества программ и основы
доказательства правильности.
Оглавление
Методы. Синтаксис методов в VB.NET
Перегрузка методов
Разделяемые (Shared) методы
Разработка иерархий наследования
Переопределение и полиморфизм
Абстрактные классы и абстрактные методы
Критерии качества программ
Выводы
Вопросы для самопроверки
Литература
Методы. Синтаксис методов в VB .NET
Классы представляют объекты реального мира, свойства классов
описывают характеристики этих объектов, методы класса должны
служить цели представления дискретных задач, решаемых объектом.
Все действия конкретного метода объекта скрыты от пользователя,
инкапсулированы в структуре класса. Сокрытие реальной реализации
метода от пользователя накладывает на разработчика метода
определенные обязательства: метод должен делать в точности то, что
о нем говорите вы. Описание того, что метод делает и как его
использовать – это «семантический контракт». Очень важно
МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ
INTERNATIONAL BANKING INSTITUTE
выполнить его правильно; если все поймут его (и будут ему
следовать), то не будет никаких проблем при изменении конкретной
реализации метода в дальнейшем. Рассмотрим основы синтаксиса
методов в VB.NET.
Методы класса являются функциями (Function) или процедурами,
подпрограммами (Sub). Функции возвращают значения, подпрограммы
– нет; это соответствует вашим имеющимся знаниям по организации
модульности программного кода. Область видимости методов
регулируется ключевыми словами: Public (доступны всем), Private
(используются только внутри самого класса), Friend (доступны внутри
класса, но при условии, что создан экземпляр класса), Protected
(доступны только внутри самого класса и наследуемых от него
классов).
Передача аргументов и параметров в методы выполняется с помощью
директивы ByVal/ByRef. Директива ByVal приводит к созданию
физической копии данных из клиентского кода (аргумент) в методе
(параметр). Передача данных ByRef приводит только к копированию
ссылки. При этом клиентский код и метод имеют различные ссылки,
которые указывают на один и тот же объект или данные, что может
привести к побочным эффектам. В качестве входных параметров
могут передаваться объекты и структуры: массивы, перечисления,
другие классы.
Перегрузка методов
Базовым принципом объектно-ориентированного проектирования
является то, что название метода должно отражать то, что метод
делает. Часто требуется иметь несколько методов, которые делают
похожие вещи, но принимают разные аргументы. Такие методы
принято называть одинаково, используя механизм перегрузки
методов. Перегрузка активно используется в библиотеках базовых
классов и может быть обнаружена в информации IntelliSense,
отображаемой при кодировании вызовов в Visual Studio. Первая
версия метода отображается с дополнительной строкой <1 of n>.
Ключевое слово Overloads позволяет определить несколько вариаций
одного и того же метода с одним и тем же именем, отличающимся
только их сигнатурой. Сигнатура (иногда называемая прототипом)
метода – это комбинация его имени и списка аргументов.
Метод CalculateCost(), который принимает два аргумента, объект
Order и Double
Private Sub CalculateCost(ByVal objOrder As Order, ByVal OrderCost As
Double)
End Sub
Имеет следующую сигнатуру:
МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ
INTERNATIONAL BANKING INSTITUTE
CalculateCost(Order,Double).
В то же время функция CalculateCost()
Public Function CalculateCost(Byval OrderCost As Double, ByVal
DeliveryFree As Double) As Double
Return OrderCost+DeliveryFree+HandilingCharge
End Function
Имеет следующую сигнатуру: CalculateCost(Double,Double).
Как можно видеть, возвращаемое значение не рассматривается как
часть сигнатуры метода. Это определяет одно из правил перегрузки:
два и более метода не могут перегружать друг друга, если они
отличаются только возвращаемыми типами.
Если вы хотите перегрузить метод, необходимо соблюдать несколько
правил:
 Каждый из перегружаемых методов должен отличаться хотя
бы одной из следующих характеристик: числом параметров,
типом данных параметров, порядком следования параметров.
 Каждый метод должен иметь одинаковое имя метода, иначе
вы просто создадите абсолютно новый метод.
 Function может перегружать Sub, если они имеют разные
списки параметров.
 Вы не можете перегрузить метод свойством с таким же
именем, и наоборот.
Разделяемые (Shared) методы
Разделяемые методы предоставляют механизм для того, чтобы
сделать метод доступным независимо от того, создан экземпляр
класса или нет. На самом деле, в отличие от других методов, вся
функциональность, содержащаяся в разделяемом методе, и все
данные, обрабатываемые и возвращаемые таким методом, являются
глобальными для всех экземпляров класса. Как следствие,
разделяемые методы используются для того, чтобы выполнять
действия, которые не зависят от конкретного экземпляра, а являются
общими для всех экземпляров класса. Например, они могут
использоваться для создания глобальных переменных и констант и
независимых функций для всего приложения. Так, многие
математические функции (Math.Sin(), Math.Cos() и прочие),
находящиеся в библиотеке классов .NET Framework, реализованы как
разделяемые методы. другим использованием разделяемых методов
является создание экземпляров классов, имеющих закрытые
конструкторы; этот вопрос рассматривается в практикуме к этой теме.
Во фрагменте кода, приведенном ниже, приведен пример
разделяемого метода SaveDrivers() для сохранения объектов типа
Driver в коллекции, передаваемой в базу данных.
МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ
INTERNATIONAL BANKING INSTITUTE
Imports System.Collections
Module SharedMethods
Public Class Driver
Private m_DriverName As String
Public Shared Function SaveDrivers(ByRef DriverCollection As ArrayList)
As Integer
Dim objDriver As New Driver
For Each objDriver In DriverCollection
‘Код для сохранения объектов в базе данных
Next
End Function
End Class
Sub Main()
‘Это коллекция, в которой мы храним данные
Dim DriverCol As New ArrayList()
‘Это объект класса Driver
Dim objDriver1 As New Driver()
‘Добавим объект в коллекцию
DriversCol.Add(objDriver1)
‘Вызов разделяемого метода из объекта класса Driver. Технически это
возможно, но приводит к путанице в коде. Смысл разделяемого
метода в том, что он не принадлежит ни одному из объектов класса,
должен вызываться из самого класса.
objDriver1.SaveDrivers(DriversCol)
‘Поскольку метод SaveDrivers() не принадлежит какому-либо
конкретному экземпляру класса Driver. Этот метод должен быть
вызван из самого класса:
Driver.SaveDrivers(DriverCol)
End Sub
End Module
Разработка иерархий наследования
Наследование позволяет определять новые классы, непосредственно
основанные на уже существующих классах нашей системы.
Наследуемый класс называется базовым классом, родительским
классом или суперклассом; вы можете выбрать тот термин, который
вам больше нравится. Обилие названий – это результат эволюции
концепции наследования.
Классы,
которые
наследуют
от
суперклассов,
называются
производными классами, дочерними классами или подклассами.
Наследование описывается отношением «является видом/подвидом»
между подклассами и суперклассами. Например, кошка является
видом животного, так что мы можем определить класс Cat как
МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ
INTERNATIONAL BANKING INSTITUTE
подкласс класса Animal. Мы говорим, что класс Cat наследует от
класса Animal. Другими словами, класс Cat расширяет класс Animal с
помощью предоставления дополнительных данных и операций,
специфичных для кошки. Следующая диаграмма показывает UMLнотацию наследования. В этом примере суперклассом является класс
BankAccount. Имеется три подкласса с именами SavingAccount,
CheckingAccount и StudentAccount, которые представляют различные
типы банковских счетов, реализованных в приложении. В этой
диаграмме треугольник является нотацией UML для обозначения
наследования (рис. 8.1).
BankAccount
SavingAccount
CheckingAccount
StudentAccount
Рис. 8.1. Иерархия наследования
Приведенный пример показывает один уровень наследования. Если
бизнес-модель предметной области этого требует, то, можно также
определить иерархии наследования, имеющие несколько уровней
глубины. Например, мы можем создать несколько накопительных
счетов, предлагающих различные процентные ставки и условия
снятия денег со счета.
BankAccount
- Balance : UInt32
- Number : UInt32
+
+
+
+
SavingAccount
Debit ()
:
Credit ()
:
PrintStatement () :
Funds ()
:
Object
Object
Object
Object
CheckingAccount
StudentAccount
- InterestRate : System.UInt16
- ChecksWritten : System.UInt32
- OverdragtLimit : System.UInt32
+ ApplyInterests () : Object
+ debitByCheck () : Object
+ IncreaseLimit () : Object
Рис. 8.2. Наследование и полиморфизм
Суперкласс определяет общие данные и операции, требуемые всеми
подклассами. Подклассы наследуют все члены суперкласса и могут,
если это необходимо, определять дополнительные члены. Диаграмма
8.2. показывает некоторые данные, и операции для простой иерархии
BankAccount. Обратите внимание на следующие моменты в этой
иерархии наследования:
Класс BankAccount определяет два члена-данных с именами Balance и
Number, потому что все виды банковских счетов нуждаются в этой
МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ
INTERNATIONAL BANKING INSTITUTE
информации. Класс BankAccount также определяет операции с
именами debit, Credit, PrintStatement и Funds, так как все виды
банковских счетов требуют проведения этих операций.
Класс SavingAccount наследует все данные и операции класса
BankAccount и определяет дополнительный член-данных (InterestRate)
и
дополнительную
операцию
(ApplyInterest),
потому
что
накопительные счета предлагают проценты.
Класс CheckingAccount наследует все данные и операции класса
BankAccoiuny
и
определяет
дополнительный
член-данных
(CheksWritten) для хранения информации обо всех чеках, которые
были выписаны для этого счета. Класс CheckingAccount также
определяет дополнительную операцию (DebitByCheck), чтобы
позволить пользователю снимать деньги по выписанному чеку.
Класс StudentsAccount наследует все данные и операции класса
BankAccount
и
определяет
дополнительный
член-данных
(OverdraftLimit), чтобы предоставить студенту возможность превысить
кредит. Класс StudentAccount также определяет дополнительную
операцию (IncreaseLimit), чтобы студенты могли повысить свой предел
превышения кредита, если они попадают в сложную финансовую
ситуацию.
Подклассы наследуют все данные и операции суперкласса. Это
позволяет нам разрабатывать подклассы быстрее, чем мы могли бы
при отсутствии механизма наследования. Единственным недостатком
является то, что подклассы зависят от суперкласса; если мы
модифицируем
суперкласс,
то,
возможно,
нам
придется
модифицировать и подклассы так, чтобы они оставались
работоспособными с изменениями суперкласса. В идеале мы могли
бы создать суперкласс так, чтобы нам никогда не потребовалось
модифицировать его в будущем.
.NET Framework (и, следовательно, Visual Basic.NET) предоставляют
возможность наследовать реализацию только от одного суперкласса;
это известно, как одиночное наследование реализации. Некоторые
языки программирования, такие как С++, позволяют классам
наследовать непосредственно от нескольких суперклассов; очевидно,
что это называется множественным наследованием реализации.
Переопределение и полиморфизм
Подкласс может переопределить операции своего суперкласса. Это
позволяет предоставлять новые типы, которые изменяют поведение
существующих типов приложения. Суперкласс указывает, какие
методы могут потребовать переопределения, а подкласс может
переопределять эти методы, если ему требуется изменить их
поведение. Обратите внимание на следующие моменты:
МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ
INTERNATIONAL BANKING INSTITUTE
Когда мы разрабатываем суперкласс, мы должны принять решение о
том, какие операции могут быть переопределены, а какие нет. Обычно
суперкласс содержит операции обоих видов. Не всегда очевидно,
следует ли определять ту или иную операцию как переопределяемую
или нет, если вы сомневаетесь, то лучшим решением будет объявить
операцию как переопределяемую.
Подкласс не обязан переопределять переопределяемые операции. Он
может наследовать существующие операции суперкласса «как есть»,
если они отвечают требованиям этого подкласса.
Если подкласс переопределяет переопределяемые операции, то он
должен определить операцию с тем же именем и той же сигнатурой,
что и операция суперкласса.
UML не предоставляет никакой специальной нотации для указания
того, что операция переопределяемая; мы полагаем, что операция
переопределяемая, если она переопределяется в одном или
нескольких классах.
На следующей (рис. 8.3) диаграмме UML CheckingAccount и
StudentAccount
переопределяют
операцию
PrintStatement,
предназначенную для распечатки дополнительной информации о
счете. Класс StudentAccount также переопределяет операцию Founds,
потому что доступные средства для студенческого счета зависят от
возможности превышения кредита.
BankAccount
- Balance : UInt32
- Number : UInt32
+
+
+
+
Debit ()
:
Credit ()
:
PrintStatement () :
Funds ()
:
Object
Object
Object
Object
CheckingAccount
SavingAccount
- InterestRate : System.UInt16
+ ApplyInterests () : Object
StudentAccount
- ChecksWritten : System.UInt32
- OverdragtLimit : System.UInt32
+ DebitByCheck () : Object
+ PrintStatement () : Object
+ IncreaseLimit ()
: Object
+ PrintStatement () : Object
+ Funds ()
: Object
Рис. 8. 3. Переопределение методов базового класса
Возможность того, что одна и та же операция может быть определена
в
большом
количестве
различных
классов,
называется
полиморфизмом. При этом реализация операции специфична для
каждого класса. Полиморфизм является очень важным аспектом
наследования; он позволяет писать код, который вызывает операции
объекта, не зная точного типа этого объекта. Например, мы можем
написать общий метод, который принимает любой вид объекта
BankAccount и вызывает его операцию Funds. Автоматически будет
вызвана корректная операция Funds, зависящая от реального типа
объекта банковского счета, переданного в метод. Это освобождает
МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ
INTERNATIONAL BANKING INSTITUTE
клиентский код от необходимости выяснять, к какому именно типу
счета принадлежит переданный объект.
Переопределенные методы у классов наследником помечаются
ключевым словом (модификатором) Overrides, а у класса родителя –
модификатором Overridable. Например,
Public Overridable Function PrintStatement() As String
Так
будет
выглядеть
описание
метода,
подлежащего
переопределению, у родительского класса BankAccount,
Public Overrides Function PrintStatement() As String
а так будет выглядеть сигнатура переопределенного метода у класса
наследника StudentAccount.
Абстрактные классы и абстрактные методы
Абстрактный класс – это класс, для которого нельзя создавать
экземпляры. Другими словами, клиентский код не может создать
объект типа абстрактного класса. Суперклассы часто объявляются
абстрактными, потому, что они не содержат достаточно информации,
чтобы предоставлять реальные объекты нашей системы; суперклассы
играют исключительно роль хранилища общих данных и операций,
требуемых их подклассам. Для того чтобы создать такой абстрактный
класс необходимо добавить ключевое слово MustInherit к оператору
описания класса: Public MustInherit Class BankAccount.
Противоположностью абстрактного класса является конкретный класс.
Клиентский код может создавать объекты типа конкретного класса.
При описании дочернего класса следует указывать ключевое слово
Inherit – наследник:
Public Class StudentAccount
Inherit BankAccount
Абстрактная операция – это операция, которая должна быть
переопределена в подклассе. По определению мы можем определить
абстрактные операции только в абстрактных классах. Абстрактные
операции полезны, поскольку они позволяют суперклассу указать
общий набор операций, который должен поддерживаться всеми его
подклассами. Клиентский код может полагаться на тот факт, что все
подклассы поддерживают операции, определенные в суперклассе.
Абстрактные методы в описании абстрактных классов должны быть
помечены ключевым словом MustOverride. Более того, подобные
методы могут иметь только заголовок (сигнатуру), но не иметь
ключевых слов End Function или End Sub. Но таким образом
описанные методы абстрактного класса должны обязательно быть
переопределены в классах наследниках.
МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ
INTERNATIONAL BANKING INSTITUTE
Критерии качества программ
Основными стандартами в области качества стали международные
стандарты серии ИСО 9000, разработанные Международной
организацией по стандартизации. Увеличивающаяся в настоящее
время конкуренция между производителями программных продуктов
приводит к установлению жестких требований к качеству этой
продукции. Для того чтобы быть конкурентоспособными, организации
должны применять эффективные системы, ведущие к повышению
качества программ и более совершенному удовлетворению
требований заказчика. Под системой качества понимается, согласно
ИСО 8402, совокупность организационной структуры, методик,
процессов и ресурсов, необходимых для осуществления общего
руководства качеством продукции, производимой организацией.
Целью руководящих положений и требований международных
стандартов ИСО 9000 является удовлетворение требований с позиции
четырех аспектов, являющихся ключевыми для качества продукции.
 Качество благодаря определению потребностей заказчиков.
 Качество благодаря конструкции, то есть качество благодаря
встраиванию в продукцию характеристик, способствующих тому,
чтобы она отвечала требованиям и возможностям рынка.
 Качество благодаря поддержанию постоянного соответствия
конструкции, реализации характеристик, заложенных в проект.
 Качество благодаря техническому обслуживанию продукции в
процессе ее эксплуатации.
Показатели качества программ устанавливает ГОСТ 28195 «Оценка
качества программных средств. Общие положения». К ним относятся:
 Функциональные
возможности.
Данная
характеристика
описывает свойства программы в части полноты удовлетворения
требований пользователя и в этом смысле является
определяющей для потребительских свойств программного
обеспечения.
 Надежность.
Специфика
программного
обеспечения
заключается в том, что оно не подвержено старению и износу, а
отказы проявляются из-за ошибок в требованиях, проекте,
реализации.
 Практичность. При оценке этой характеристики следует исходить
из требований пользователя.
 Эффективность.
Оценка
данной
характеристики
также
критически зависит от требований пользователя. Программа
может оказаться неэффективной не в силу плохого кодирования,
а в силу противоречивости и нереальности исходных
требований.
МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ
INTERNATIONAL BANKING INSTITUTE
 Сопровождаемость. Мобильность. Для этих двух характеристик
следует учитывать, что в специфических российских условиях им
часто не уделяется достаточно внимании со стороны
пользователя. Эти характеристики связаны с долгосрочным
планированием
развития
программного
обеспечения.
Сопровождаемость – набор атрибутов, относящихся к объекту
работ, требуемых для проведения конкретных изменений
(модификации). Мобильность – набор атрибутов, относящихся к
способности программы быть перенесенной из одного окружения
в другое.
Выводы
Методы класса являются скрытой от пользователя реализацией
функциональности класса. Продуманное описание сигнатуры метода
позволит точно сформулировать «семантический контракт» с
пользователем. Правильно названные и определенные методы
делают приложение более легким для понимания и дальнейшего
сопровождения. Для эффективного использования методов важно
понимать, какие типы требуется передать в метод, что с ними при
этом произойдет и что будет из метода возвращено. Если вы будете
следовать рекомендациям, приведенным выше, то классы, созданные
вами, будут иметь достаточное качество для построения на их основе
приложений, исключающих побочные и нежелательные эффекты.
Мы можем создать наши собственные иерархии, содержащие
суперклассы и подклассы, чтобы удовлетворить потребности нашей
бизнес-модели, но необходимо помнить, что все типы данных
наследуются (либо напрямую, либо через промежуточные классы) от
класса общей иерархии наследования System.Object.
Показатели качества программ устанавливает ГОСТ 28195 «Оценка
качества программных средств. Общие положения».
1.
2.
3.
4.
5.
6.
Вопросы для самопроверки
Для чего используется механизм перегрузки методов класса, не
проще было бы давать различные имена методам?
Можно ли вызвать разделяемый (Shared) метод у объекта
класса? Получите ли вы при этом какое-либо системное
сообщение?
Является ли перегрузка частным случаем полиморфизма?
Как
вы
считаете,
можно
ли
переопределить
уже
переопределенный метод?
Для чего создаются абстрактные классы и абстрактные методы?
Можно ли не переопределять в собственном классе метод
ToString?
МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ
INTERNATIONAL BANKING INSTITUTE
7. Существует ли ограничение на глубину иерархии в дереве
наследования?
8. Класс, являющийся базовым в вашей иерархии классов, должен
быть абстрактным или это не обязательно?
9. Все методы абстрактного класса должны быть объявлены как
абстрактные методы?
1.
2.
3.
4.
Литература
Чакраборти А., Кранти Ю., Сандху Р.Дж. Microsoft .NET
Framework: разработка профессиональных проектов: Пер. с
англ. — СПб.: БХВ-Петербург, 2005. – 896 с.: ил.
Ольсен Э., Эллисон д., Спир Дж. Visual Basic .NET. Разработка
классов. Справочник/Практ. Пособ./Пер. с англ. — М.
Издательство «СП ЭКОМ», 2006. – 416 с. ил.
Троелсен Э. C# и платформа .NET. Библиотека программиста —
СПб.: Питер, 2006 — 796 с.: ил.
Visual Basic.NET: учебный курс/ В.Долженков, М.Мозговой. —
СПб.: Питер, 2005. – 465 с,: ил.
Download