Клас диаграмите са стандартно графично средство за изобразяване на йерархии от типове, предоставено ни от езика за моделиране UML (Unified Modeling Language). Ще се запознаем съвсем накратко с клас диаграмите без да претендираме за изчерпателност, тъй като моделирането с UML е необятна тема, на която са посветени хиляди страници и тази материя е извън обхвата на настоящата тема.
При многократно наследяване е възможно да се получат йерархии, които са големи и сложни и по тази причина са трудни за възприемане. Чрез клас диаграмите се създава визуална представа за взаимовръзките между типовете и така се улеснява възприемането им. С помощта на клас диаграмите можем да погледнем системата, която разработваме "от птичи поглед", което ни помага да си създадем значително по-ясна представа за нея, отколкото ако преглеждаме множество файлове със сорс код.
Изобразяване на типовете и връзките между тях
В UML клас диаграмите типовете се изобразяват като правоъгълници, в които са изписани членовете им, евентуално с отбелязана степен на видимост пред името: + за public, # за protected и - за private. Ето един пример (класът Rectangle):
Правоъгълникът, изобразяващ даден тип, обикновено е разделен на три части – най-горната съдържа името му, средната съдържа полетата му и най-долната съдържа неговите методи.
Наследяване
Наследяването на клас и имплементирането на интерфейс се изобразява със затворена стрелка (), като стрелките, обозначаващи наследяване и имплементиране се различават по това, че първите обикновено са плътни, а вторите – пунктирани:
В примера класът FilledRectangle наследява класа Rectangle, а класът Square имплементира интерфейса ISurfaceCalculatable, а.
Асоциация, агрегация, композиция
Връзките между типовете се изобразяват с отворена стрелка (). Тези връзки се наричат още асоциациационни връзки (association links).
Асоциационните връзки могат да бъдат три вида (асоциация, агрегация, композиция). Асоциация е просто някаква връзка между два типа, примерно даден студент използва даден компютър (асоциацията е между студента и компютъра). Агрегация означава че даден клас съдържа много инстанции на даден друг клас, но вторият може да съществува отделно и без първия, примерно една учебна група се състои от много студенти, но студентите могат да съществуват и самостоятелно, без да са в дадена учебна група. Композиция между два класа означава, че един клас се използва като съставна част от друг и не може да съществува без него, примерно един правоъгълник се състои от 4 страни, но страните не могат да съществуват самостоятелно без правоъгълника.
Множественост на връзките
Връзките композиция и агрегация могат да имат множественост, например "1 към 1", "1 към много" и т.н. Пример за множественост на връзка е връзката между студент и учебна дисциплина (например "1 към много" – 1 студент изучава много учебни дисциплини).
Клас диаграми – пример
Следният пример представлява проста диаграма и илюстрира основните елементи, които ни предоставя UML нотацията за изграждане на клас диаграми:
По затворените стрелки разбираме, че класовете Square и Rectangle наследяват Shape и имплементират интерфейса ISurfaceCalculatable, а те от своя страна са наследени съответно от FilledSquare и FilledRectangle.
Виждаме също как с отворени стрелки е изобразена връзката "тип съдържа инстанция на друг тип като свой член", както например класът FilledRectangle съдържа инстанция на структурата Color.
Пространства от имена (namespaces)
Пространствата от имена (namespaces) са средство за организиране на кода в софтуерните проекти. Те съдържат дефиниции на класове, структури, изброени типове и други пространства от имена, като по този начин осигуряват логическо групиране на множества от типове. Пространствата от имена не могат да съдържат дефиниции на функции и данни, тъй като езиците от .NET Framework са строго обектно-ориентирани и такива дефиниции се допускат само в тялото на типовете.
Дефиниране
Пространства от имена в C# се дефинират и използват подобно на пространствата от имена в C++ и на пакетите в Java. Задават се с ключовата дума namespace последвана от името на пространството и множеството от дефиниции на типове, оградено във фигурни скоби, както е показано на примера по-долу:
namespace SofiaUniversity
{
// Type definitions ...
}
|
Тази дефиниция може да присъства в повече от един файл, като по този начин се създава пространство, което е физически разпределено в различните файлове.
Достъп до типовете
Достъпът до дефинираните в тялото на пространство типове се осъществява по два начина – чрез използване на пълно име на типа и с използването на ключовата дума using.
Пълно име наричаме името на типа предшествано от името на пространството, в което се намира, разделени с точка. Например ако класът AdministrationSystem е дефиниран в пространството SofiaUniversity, тогава пълното му име е AdministrationSystem.SofiaUniversity. По този начин се обръщаме към имена на типове, дефинирани в пространства, различни от текущото.
Използването на пространства от имена позволява дефинирането на типове с едно и също име, стига те да са в различни пространства. Посредством използването на пълни имена се разрешават конфликтите, породени от еднаквите имена на типовете. Например клас с име Config може да е дефиниран както в пространството SofiaUniversity. DataAccess, така и в SofiaUniversity.InternetUtilities. Ако е необходимо в даден клас да бъдат използвани едновременно и двата класа, те се достъпват с пълните си имена: SofiaUniversity.DataAccess.Config и SofiaUniversity.InternetUtilities.Config.
Ключовата дума using
Директивата using , поставена в началото на файла, позволява директно използване на всички типове от указаното пространство само чрез краткото им име. Пример за това е следният фрагмент от кода, който се генерира автоматично от Visual Studio .NET при създаването на нов файл:
Това обръщение прави достъпно за програмата основното пространство от имена на .NET Framework – System, което съдържа някои типове, които се използват постоянно – Object, String, Int32 и др.
Подпространства
Както вече споменахме, пространствата от имена могат да съдържат и дефиниции на други пространства. По този начин можем да създаваме йерархии от пространства от имена, в които да разполагаме типовете, които дефинираме.
Подпространства могат да бъдат дефинирани в тялото на пространството родител, но могат да бъдат създадени и в отделен файл. В такъв случай се използва пълно име на пространство от имена. То представлява собственото име на пространството предшествано от родителите му, разделени с точки, както например System.Windows.Forms. Пълното име на тип, дефиниран в подпространство, трябва да съдържа пълното му име, например System.Windows.Forms.Form.
Следва да илюстрираме дефинирането на една простра структура от пространства от имена:
namespace SofiaUniversity.Data
{
public struct Faculty
{
// ...
}
public class Student
{
// ...
}
public class Professor
{
// ...
}
public enum Specialty
{
// ...
}
}
namespace SofiaUniversity.UI
{
public class StudentAdminForm : System.Windows.Forms.Form
{
// ...
}
public class ProfessorAdminForm : System.Windows.Forms.Form
{
// ...
}
}
namespace SofiaUniversity
{
public class AdministrationSystem
{
public static void Main()
{
// ...
}
}
}
|
В примера по-горе виждаме дефинициите на основното пространство SofiaUniversity и подпространствата му SofiaUniversity.Data и SofiaUniversity.UI, в които сме дефинирали нашите потребителски типове, например класовете SofiaUniversity.AdministrationSystem и SofiaUniversity.UI.StudentAdminForm, и структурата SofiaUniversity. Data.Faculty.
Използвайки директивата using можем да включваме пространства, зададени с пълното им име. Тази директива включва единствено това пространство, което споменаваме изрично, но не и неговите подпространства. Например, ако укажем using System.Windows няма да имаме директен достъп до класа System.Windows.Forms.Form.
Използвайки ключовата дума using можем да задаваме също и псевдоними на пълните имена на пространствата, както например:
using WinForms = System.Windows.Forms;
namespace SofiaUniversity.UI
{
public class StudentAdminForm : WinForms.Form
{
// ...
}
// ...
}
| Как да организираме пространствата?
Основната цел на използването на пространства от имена е създаването на добре организирани и структурирани софтуерни системи. За целта трябва да разделяме типовете, които дефинираме, в пространства, чиято структура отговаря на логическата организация на обектите с които работим. Ако се придържаме към някои прости принципи при изграждането на структури от пространства и типове, можем да създадем значително по-ясни и интуитивни за възприемане проекти без да рискуваме вместо това допълнително да си усложним живота.
Логическа организация
Изключително полезно е да разпределяме типовете, които дефинираме, в пространства от имена. Това е задължително, ако те са много на брой, например над 20, защото прекалено много елементи на едно място са по-трудни за възприемане не само в програмирането. Можем да създаваме и вложени пространства, но само ако е необходимо - не трябва да изпадаме и в другата крайност, защото ако създаваме прекалено много пространства от имена ще се окажем с излишно сложна структура от пространства, която няма да направи организацията в проекта ни по-ясна, даже напротив.
Физическа организация
Добре е логическата организация в системите, които разработваме, да отговаря на физическата – публичните типове да създаваме във файлове, носещи тяхното име, а за пространствата – директории с тяхното име, в които да се поместват типовете им. Когато създаваме вложени пространства, е добре да ги създаваме като поддиректории на тези на родителите им пространства. Така само с един поглед на структурата на проекта в Solution Explorer на Visual Studio .NET добиваме представа за нея.
За проекта от примера по-горе е удачно да организираме типовете във файлове по следния начин:
Виждаме, че класът Student от пространството SofiaUniversity.Data е разположен във файла Student.cs от поддиректорията Data на директория SofiaUniversity от нашия проект. По същия принцип класът ProfessorAdminForm се намира във файла SofiaUniversity/UI/ ProfessorAdminForm.cs.
При такава организация е много лесно да се запознаем визуално и с логическата, и с физическата структура на компонентите, които изграждат проекта ни. Когато двете не се разминават, навигацията в сорс кода на системата и като цяло работата с нея се улеснява значително.
Сподели с приятели: |