UML’de Class Diyagramları (İleri Düzey)

Class diyagramları ile alakalı önceki yazımda, onların temel özelliklerinden ve gösterimlerinden bahsettik. Temel olarak bir sınıfın UML diyagramında öznitelikleri (attributes) ve işlemleri (operations) bulunmaktaydı. Ayrıca sınıflar arasındaki ilişkileri göstermek için pek çok farklı ilişki türü kullanıyorduk. Biz önceki yazıda Association ve Generalization ilişkilerini inceledik. Aslında o yazıda yazdıklarımızı öğrendiyseniz, kendinize ait bir Class diyagramı tasarlamanız çok da zor olmayacaktır. Bu yazıda ise UML’deki Class diyagramları ile ilgili daha derin özellikleri inceleyecek ve kalan ilişki türlerini de anlatacağız.

İlk olarak Dependency (Bağımlılık) ilişkisinden başlayalım. Dependency, iki eleman arasında varolan ve bir elemanın herhangi bir tanımında değişiklik olduğunda, bundan diğer elemanın da etkilendiği bir ilişki türüdür. Bir sınıf bir başka sınıfı veri olarak tuttuğunda veya bir sınıfın üye fonksiyonunun (member function) parametre türü bir başka sınıf türünden olduğunda bu gibi bir ilişki ile kaşılaşmak mümkündür. Class diyagramında bu gibi bir ilişkiyi, iki sınıf arasına kesikli çizgiye sahip bir ok çizerek gösterebiliriz:

Figür 1: Depedency İlişkisinin Diyagramda Gösterimi

Peki bunun Association ilişkisinden ne farkı vardır? İnternette biraz araştırma yaptığınızda aslında belli başlı farklar ortaya çıksa da Dependency oldukça geniş bir kavram olduğundan net farkları ortaya koymak zordur. Ama genel olarak şu çıkarımı yapmak mümkündür: Association’larda bir sınıf başka bir sınıf ile referans bazlı bir ilişki içindeyken, Dependency’lerde ise genellikle bir sınıfın parçası olan bir şeyi çağırdığınızda (işlem/metot) ortayan çıkan bağımlılık ilişkisi vardır. Örneğin aşağıdaki kodda A sınıfı ile B sınıfı arasında bir Association ilişkisi vardır:

class A {};

class B {
    A someName;
    B(A someTempName);
};

Ancak aşağıdaki kodda ise bir Dependency ilişkisinden söz edilebilir:

class A {};

class B {
    void someFunction(A parameterName);
};

İkinci kodu ele aldığımızda, eğer A sınıfının arayüzünde (interface) herhangi bir değişiklik olursa, bu durumda someFunction() fonksiyonunun implementasyonu da büyük ihtimalle değişecektir. Bu da B sınıfını değiştirmemiz anlamına gelir. Ancak ilk kodda A sınıfı değişse de B sınıfı onu sadece bir veri elemanı olarak tuttuğundan, herhnagi bir değişiklik yapması gerekmez. Ancak yine de bu iki sınıf arasında bir ilişki bulunur. İşte fark bundan ibarettir. Elbette bir sınıf çok farklı Dependency türleri ile bir sınıfa bağımlı olabilir. Bunlara ve bunları nasıl belirteceğimize birazdan değineceğim.

Bir Dependency bağımlılığında kullanılan (yani bağımlı olunan) sınıfa Supplier (Sağlayıcı) adı verilirken, bu bağımlılığı kullanan (yani bağımlı olan) sınıfa ise Client (İstemci) adı verilir. Yukarıdaki figürde göreceğiniz gibi Dependency okunun yönü Client’tan Supplier’a doğrudur. Dependency’ler çok katmanlı olduğunda (yani bir sınıfın diğerine, onun da bir başkasına bağımlılığı olduğunda) işler her zaman sandığınız gibi gitmeyebilir. Öncelikle bu tip Dependency ilişkileri geçişli (transitive) bir yapıda değildir. Yani A sınıfı B sınıfına bağımlıysa ve B sınıfı da C sınıfına bağımlıysa, bu durum A sınıfının C sınıfına doğrudan bağımlı olduğunu göstermez. Elbette bazı istisnalar bulunsa da genellikle durum böyledir.

Figür 2: Çok Katmanlı Dependency Örneği

Konuya devam etmeden önce UML diyagramları ile ilgili çok önemli bir özelliği açıklamakta fayda görüyorum. Bildiğiniz gibi grafiksel modelleme dilleri kullanıcıya görsel olarak kolay bir şekilde bilgi vermeyi amaçlar. Ancak onların da yeri geldiğiinde bazı zorlukları vardır. Örneğin bir grafiksel dilde çok fazla sembol varsa, bu sembollerin ne anlama geldiğini bilmeniz gerekir. Yoksa bir diyagramdan hiçbir şey anlamayabilirsiniz. UML bu tip sembollerin sayısını her zaman azaltmayı amaçlar. Bunun için bir sembolün farklı amaçlar için kullanılabilmesine izin verir. Bunu da Keyword (Anahtar Kelime) adı verilen araçlar ile yapar.

Keyword’ler bir UML diyagramında olan sembolün anlamını değiştirmeyi veya onu farklı bir açıdan ele almayı sağlayan kelimelerdir. Bunlar ile diyagramda olmayan gösterimleri bile kendiniz oluşturabilirsiniz. Keyword’ler iki açılı parantez arasına (<<>>) yazılırlar. İleride göreceğimiz <<interface>> buna örnek olarak verilebilir. Bu kelimlerin yerine bazen semboller de kullanılsa da başta söylediğim karmaşıklığın aynısına yol açabileceği için pek tercih edilmez. Aslında bazı Keyword’ler süslü parantezler arasında ({abstract} gibi) da gösterilebilir. Ancak nerede hangi gösterimin kullanılacağına dair bir açıklık yoktur.

UML’de bazı Keyword’ler oldukça sık kullanıldığından kısa bir biçimde yazılabilirler. Örneğin <<interface>> yerine <<I>> ve {abstract} yerine {a} kullanılabilir. Bu kısaltmalar bazen çok kullanışlı olsalar da standart olmadıklarından kullanırken dikkatli olmanız gerekir. UML 1’de açılı parantezler ile olan gösterimler Stereotype olarak adlandırılırdı. UML 2 ile birlikte Stereotype tanımı oldukça genişlediğinden, neyin bir Stereotype neyin ise bir Keyword olduğu konusu biraz tartışmalıdır. Günümüzde UML 1’e alışkın kişilerin çoğu doğru olsun veya olmasın, Keyword yerine Stereotype kelimesini tercih ederler. Ancak ben burada Keyword kelimesini kullanmayı daha uygun buluyorum.

Şimdi Keyword’lerden bahsettikten sonra Dependency konumuza geri dönelim. Dediğim gibi, Dependency ilişkisinin pek çok farklı türü bulunmaktadır. Bu türleri belirtmek için Dependency okunun üzerine bir Keyword yerleştirmemiz gerekir. Fakat bunlar genel bir fikir vermek ve ortalığı karıştırmak istemeyen UML diyagramlarında pek kullanılmazlar. Ben yine de bu Keyword’lerden bazılarını aşağıdaki tabloda verdim:

KeywordAnlamı
<<call>>Client içindeki bir işlemin Supplier içindeki bir işlemi çağırır.
<<create>>Client içindeki bir işlem Supplier türünden bir nesne ile geri döner.
<<derive>>Client içindeki bir değer, Supplier tarafından hesaplanır.
<<instantiate>>Client içindeki bir işlem Supplier türünden bir nesne oluşturur.
<<send>>Client içindeki bir işlem Supplier’a bir sinyal (signal) gönderir.
<<substitute>>Client’taki bir eleman Supplier ile (bir şekilde) yer değiştirebilir.
<<use>>Client’ın bir elemanı Supplier’ı bir şekilde kullanır.

Gördüğünüz gibi Dependency ilişkileri ile bir sınıfın hemen hemen her yerinde karşılaşmamız mümkündür. Ancak tasarım aşamasında asıl yapmamız gereken şey, Dependency’lerin sayısını azaltmak olmalıdır. Proje yönetiminde Dependency’ler bazen oldukça baş ağırtabilir. Bunun yanında eğer bir sınıfta çok fazla Dependency varsa, bunların hepsini de diyagramda göstermeye çalışmak ve hatta yukarıdaki Keyword’leri de kullanmak çoğu zaman gereksiz bir iş olacaktır. Bu durumda sadece önemli Dependency’leri göstermeniz yeterlidir. Çünkü çok fazla Dependency gösterimi, diyagramı okuyan kişiye fayda sağlamaktan çok zarar verecektir.

Bildiğiniz gibi programlamada tanımladığınız nesnelerin belirli ömürleri vardır. Bazıları tanımlandıkları bloğun sonuna geldiğinde ömrünü tamamlarken, bazıları ise programın sonuna kadar varolmaya devam ederler. Birçok programlama dilinde bulunan statik ömürlü nesneler ikinci söylediğim gruba girerler. C++, Java ve C# gibi dillerde tanımladığımız değişken veya fonksiyonların statik ömürlü olduğunu belirtmek için başına static anahtar kelimesini yazarız. UML diyagramlarında da sınıftaki bir özniteliğin (attribute) veya bir işlemin (operation) statik ömürlü olduğunu anlatmak için, onu altı çizili olarak yazarız. Aşağıdaki figürde verilen get metodu buna örnektir:

Figür 3: Statik Üyelerin Gösterimi[1]

Bu küçük bilgiyi verdikten sonra sınıflar arası ilişkilerden devam ediyoruz. Association ve Dependency kavramları ortalığı çok karıştırmıyormuş gibi, bunlara iki kavram daha ekleyeceğiz: Aggregation (Toplanma) ve Composition (Birleşim). Bir nesne başka bir nesne ile Aggregation ilişkisine sahipse, bu iki nesne birbirineden bağımsız olarak yaşamını sürdürebilir. Ancak bir nesne başka bir nesne ile Composition ilişkisine sahipse, ikinci nesne ilk nesnenin bir parçasıdır ve ondan bağımsız yaşamını sürdüremez anlamı çıkar. Aggregation gösteriminde çizginin bir tarafında içi boş eşkenar dörtgen yer alırken, Composition gösteriminde ise içi dolu eşkenar dörtgen yer alır:

Figür 4: Aggregation ve Composition Gösterimi

Klasik araba analojisi üzerinden durumu açıklamaya çalışalım. Bir araba nesnesi bir motor nesnesine ve bir de sürücüye sahiptir. Burada araba, motor ve sürücüyü birer sınıf olarak ele alırasak bu sınıflar arasında bir ilişki olmak zorundadır. Bir araba motorsuz olamaz. Yani araba motoru ile beraber araba olarak anılır. Motor da kendi başına araba olmadan bir anlam ifade etmez. Ancak arabanın sürücü ile ilişkisi böyle değildir. Bir araba, onun kullanımı için bir sürücüye ihtiyaç duyar. Ancak araba sürücü olmadan araba olmaktan, sürücü de araba olmadan sürücü olmaktan çıkmaz. Yani araba ile sürücü arasında bir Aggregation ilişkisi, araba ile motor arasında ise bir Composition ilişkisi vardır. Bu örnek ile ilgili C++ kodu ve bunun UML diyagramı aşağıda verilmiştir:

class Engine {};
class Driver {};
class Car {
    Engine engine;
    Driver *driver;
public:
    Car(Engine eng)
    {
        engine = eng;
        driver = new Driver();
    }
};
Figür 5: Aggregation ve Composition Örneği

Bunun gibi bir örneğin bu kavramların anlamlarını hemen hemen yansıtabileceğini düşünüyorum. Burada eşkenar dörtgenin, diğer sınıfları kullanan sınıf tarafında kaldığına dikkat edin. Genellikle uygulamada Association ile bu kavramlar arasında bazı belirsizlikler bulunduğundan, tasarımcılar genellikle Aggregation gösterimini tercih ederler. Ayrıca Composition farklı programlama dillerinde farklı biçimlerde ele alınabileceğinden, karmaşıklık oluşturmaması açısından onun kullanımını tercih etmezler. Ancak siz yine de hepsinin ne anlama geldiğini bilin. Association, Aggregation ve Composition ilişkisini tam karşılamasa da özetleyen diyagram şöyledir:

Figür 6: 3 İlişki Türü Arasındaki Bağlantı[2]

Nesne yönelimli programlama paradigmasını destekleyen dillerde Abstract Class ve Concrete Class kavramları karşımıza çıkar. Abstract Class (Soyut Sınıf), en az bir metodu implement edilmeden bırakılmış olan sınıflardır. Bu sınıflar türünden nesneler doğrudan oluşturulamazlar. Ondan türetme yapıp metotların implementasyonlarını gerçekleştirmek gerekir. Concrete Class (Somut Sınıf) ise bütün metdolarının implementasyonu yapılmış olan ve onun türünden nesneler oluşturabileceğimiz sınıflardır. Aslında şu ana kadar gördüğümüz tüm sınıflar bir Concrete Class örneğidir.

Java ve C# gibi dillerde abstract anahtar kelimesi ile, C++ dilinde ise virtual anahtar kelimesi ile bir metodu Abstract Method (Soyut Metot) haline getirebiliriz. Aslında C++’ta bunlara Virtual Method (Sanal Metot) adı verilmektedir. Bu metotlara, o sınıftan türetilen sınıflar tarafından yeni implementasyonlar verilebilir. Bunu yapmaya ise Override (Baskılama) adı verilir. Bunlara başta bir implementasyon sağlayabileceğiniz gibi, onları bunu yapmadan da bırakabilirsiniz. C++’ta tanımlanmış bir Virtual Method’a başta bir implementasyon sağlamazsanız, ona artık Pure Virtual Method (Saf Sanal Metot) adı verilir. Bunları sadece kısa bir özet olarak veriyorum. Normalde bu konular bu yazının kapsamı dışındadır.

Figür 7: Abstract Class ve Abstract Method Gösterimi[3]

Yukarıdaki figürde görüldüğü gibi Abstract Method’lar ilk bildirildikleri sınıfta italik olarak, Override edildikleri sınıfta ise normal bir şekilde yazılırlar. Abstract Method’a sahip Abstrack Class’ın ismi de italik olarak yazılır. Bazen italik yazmak okunabilirliği azalttığından, bu tip metotların sonuna {abstract} Keyword’ünü yerleştirmek de bir seçenektir. Interface (Arayüz) ise Java ve C#’ta onunla aynı isimdeki anahtar kelimeler ile tanımlanan, C++’ta ise bütün metotları Pure Virtual Function olan sınıflara verilen isimdir. Yani bir Interface, adı üstünde, sadece bir taslaktan ibarettir. Bu sınıftan türetilen sınıfa neleri tanımlayacağını (implement edeceğini) bildirir. Interaface’ler sınıf adının başına <<interface>> Keyword’ü getirilerek diyagramda gösterilirler:

Figür 8: Interface Gösterimi[4]

Bir Interface’i belirttiğinizde, onun soyut olan metotlarını italik olarak göstermenize ve ondan türeyen sınıfların içinde bu metotları yazmanıza gerek kalmaz. Yukarıdaki figürden anlayabileceğiniz gibi burada bir türetme yapılmıştır. Ancak bu sefer türetme okunda düz çizgi değil, kesikli çizgi kullanılmıştır. Bu aslında bizim son ilişki türümüzdür ve bu ilişki türü Realization (Gerçekleştirme) veya Implementation (İmplementasyon) olarak adlandırılır. Bu ilişki türü bir Interface’ten türetme yapmayı belirtir ve bir Interface’i (adından da anlaşılacağı gibi) implement etmenin bir belirtisidir. Abstract Class’lardan türetme yaparken yine normal Generalization sembolünü kullanabilirsiniz.

Böylelikle Class diyagramlarındaki 6 temel ilişki gösterimini görmüş olduk. Şimdi ise Association ilişkilerinin gelişmiş özelliklerinden biraz bahsedelim. Geçen yazıda Bidirectional (Çift Yönlü) Association’lardan bahsetmiştim. İki sınıf da birbirleri türünden nesneleri kullanıyorsa, bu Association türü mümkün olabiliyordu. Özellikle bu tip Associasition’ların her bir ucuna, kullanılan özniteliğin ismini yazarız. Fakat bazıları tek bir fiil kullanarak Association’ı isimlendirmektedir. Ayrıca bu fiilin yanına kimden kime etki edileceğini bildiren bir ok yerleştirilir. Hemen bir örnek verelim:

Figür 9: Association Gösteriminde Fiil Kullanımı[5]

Bu tip bir gösterim genellikle iki taraftaki okları (navigability arrows) kaldırıp daha temiz ve yüksek seviyeli bir gösterim sunmak için kullanılmaktadır. Bunu da bilmenizde fayda var. Association’lar ile ilgili bir başka özellik ise Qualified (Nitelikli) Association kavramıdır. Genellikle bu tip Association’lar bir programlama dilinde Map, Hash ve Dictionary gibi yapıların anahtar-değer ikilisini kullandığı durumlarda karşımıza çıkar. Örneğin bir ürün karaloğunun (product catalog) birden fazla açıklaması (product description) varsa, bunu UML diyagramında aşağıdaki figürün (a) kısmındaki gibi gösterebiliriz:

Figür 10: Qualified Association Örneği[6]

Fakat yazdığımız sistemde her bir ürün kataloğunu itemID isimli bir anahtar ile elde ediyorsanız, bunu yukarıdaki figürün (b) kısmındaki gibi göstermeniz mümkündür. Bu durumda itemID bir Qualifier (Niteleyici) ismini alır ve sınıf gösterminin hemen yanında bir çıkıntı olarak gösterilir. Ayrıca Association’ın diğer tarafındaki Multiplicity değerini "1..*" değerinden "1" değerine azaldığına dikkat edin. Qualified Association kullanılan UML diyagramlarında Multiplicity artık iki sınıf arasındaki çokluk ilişkisini değil, bir sınıf ile Qualifier arasındaki çokluk ilişkisini göstercektir. Bu nedenle bunu dikkate alarak UML diyagramlarını hazırlamalısınız.

Association’lar ile ilgili son bahsedeceğimiz özellik ise Association Class‘tır. Association Class’lar bir Association ilişkisine öznitelik ve işlemler eklemeye izin verir. Bu nedenle bu Association’a bağlı bir sınıf gibi gösterilir. Örneğin bir öğrenci (student) birden fazla kursa (course) kayıt (enrollment) olabilir. Burada aslında öğrenci ve kurs arasında bir Association olduğunu söyleyebiliriz. Ancak sadece bu ilişki yeterli değildir. Çünkü kayıdın ne zaman yapıldığı gibi bilgiler aslında bu Association ile ilişkili bilgilerdir. Bu anlattığımız sistemin Association Class kullanılarak gösterimi aşağıdaki gibi olur:

Figür 11: Association Class Örneği

Aslında bunu araya bir Enrollment isimli sınıf yerleştirip her iki sınıfla da Association ilişkisi kurarak da yapabilirdik. Ancak burada Association Class kullanmamızın amacı bir öğrenci ile kurs kombinasyonunda sadece bir kayıt olabileceğini net bir şekilde göstermektir. Association Class işte bu kısıtlamayı belirtmek için kullanılır. İki öğe arasındaki ilişkide belli kısıtlamalar varsa, bu kısıtlamalar bir Association Class ile gösterilebilir. Bu arada Association Class’ların programlama dilleri ile implementasyonunda herhangi bir kural yoktur. Onlar ayrı bir sınıf olarak ya da sınıfların öznitelikleri olarak implement edilebilirler.

C++ dili başta olmak üzere, özellikle genel programlama (generic programming) paradigmasını destekleyen dillerde Parameterized Class (Parametreleştirilmiş Sınıf) veya Template (Şablon) kavramı yer almaktadır. Bu kavram özellikle dildeki çeşitli elamanları tutan yapılarda (vector, list gibi) elemanın türünden bağımsız olarak bu yapıların implementasyonunu sağlar. Böylelikle onları herhangi bir tür ile kullanabilirsiniz. Örnek olarak Vector isimli bir sınıfın Template’ler ile tanımlanıp int veri türü için kullanımı aşağıdaki gibidir:

template< typename T >
class Vector{
private:
    T* element;
    int size;
public:
    explicit Vector(int sz);
};

Vector<int> a(5);  // Create Vector with 5 elements

Bu tür sınıfları da UML diyagramlarında göstermemiz mümkündür. Burada typename T ile aslında sınıfa vereceğimiz türü T ile parametreleştirmiş olduk. Bu nedenle T burada Template’in parametresi olarak anılır. İşte bir Class diyagramında bu parametreyi, sınıfın sağ üstündeki bir kutucuğa yazıp gösteririz. Aşağıdaki figürde, yukarıda yazdığımız Vector isimli Template’in Class diyagramında gösterimi verilmiştir:

Figür 12: Template Gösterimi

Bu şekilde gösterdiğiniz Class Template’lerini normal bir sınıfı kullandığınız her yerde kullanabilirsiniz. Bunun yanında yine C benzeri dillerde Enumeration kavramı vardır. Bununla tamsayıları belli bir isim grubu altında isimlendirmeniz mümkündür. Örneğin (en sık verilen örneklerden birisi) programınızda bazı renkler kullandığınızı ve bu renkleri belli sayılar ile (0: kırmızı, 1: yeşil, 2: mavi) temsil ettiğinizi düşünün. Sayıları bu şekilde belli anlamlar ifade etmek için kullanmak, programcılar açısından sıkıntılı zamanlar yaşatacağından Enumeration’lar tercih edilir. Enumeration’ları UML diyagramında <<enumeration>> Keyword’ü ile bir sınıf tanımlaması yaparak ve içine isimleri yazarak gösterebilirsiniz:

Figür 13: Enumeration Gösterimi

Bu yazıda UML’de Class diyagramları ile ilgili bazı detaylı özelliklerden bahsettik. Bu özelliklerin çoğu çok sık kullanılmasa da bazı projelerde oldukça fayda sağlarlar. Kullandığınız bazı çizim programları bu özellikleri otomatik olarak desteklemeyebilir. Bunu kullandığınız aracın Web sayfasından o aracın özelliklerine bakarak anlayabilirsiniz. Genellikle UML 2 destekleyen araçlarda bu özelliklerden çoğu bulunsa da yine de UML 2 desteklediğini iddia aden bazı araçlarda çok azına rastlarsınız.

REFERANSLAR

  1. http://3.bp.blogspot.com/-4d8ZYaG81f4/T40ODf1txNI/AAAAAAAAASY/XUpinAQEnc8/s1600/class_diagram_static_method.png
  2. https://i.stack.imgur.com/jNyV5.jpg
  3. https://www.freedgo.com/public/themes/freedgo/uml/class3.png
  4. https://developer.ibm.com/articles/the-class-diagram/
  5. https://www.modernanalyst.com/Careers/InterviewQuestions/tabid/128/ID/1807/What-is-a-navigable-association-in-UML.aspx
  6. https://www.informit.com/articles/article.aspx?p=1398623&seqNum=16
5 3 votes
Article Rating
Subscribe
Bildir
guest

0 Yorum
Eskiler
En Yeniler Beğenilenler