Single Responsibility Principle (SRP) – Tek Sorumluluk Prensibindeki amaç tek bir görev ve sorumluluk içeren sınıflar ya da fonksiyonlar oluşturmaktır. Her ne kadar tanımında uygulanması ve anlaşılması kolay görünsede birçok yazılım projesinde hatta ufacık kod örneklerinde bile bu prensibin çiğnenip üzerine basıldığını görürüz. Aslında çoğu durumda yazılımcı bu prensibi ezdiğinin farkına varmaz. Sınıfı ya da fonksiyonu oluştururken bu prensibin çiğnenmesi koda sinsize girer. Örneğin bir modülden diğer modüle mesaj/paket gönderen sınıf ya da fonksiyon yazılırken yazılımcı, send, receive ve paket oluşturma görevlerini aynı sınıfın içine koyduğunu düşünelim. Dışarıdan bakınca sanki bu şekilde olması gerektiği gibi geliyor. İşte yukarıda bahsettiğim sinsice koda giriş bu şekilde oluyor. İşin kötüsü sınıf ya da fonksiyon tamamlandıktan sonra kod gözden geçirme oturumlarında gelen itirazlar sonrasında sırf başıma iş çıkmasın diye “ya onlar bir bütün bırakalım öyle kalsın” itirazları geliyor. Unutmayalım ki yazılımda/projede bir düzen nasıl başladıysa o şekilde katlanarak gider. Eğer kurallara uygun temiz kod yazılır ise proje ilerledikçe sadelik ve akıcılık artar ama tam tersi bir kere elinize kirli kod bulaşır ise mümkünatı yok geri alamazsınız. O yüzden siz siz olun elinize kirli kod değdirmeyin.
Şimdi yukarıdaki örneği temizleyelim. Kullanıcı send ve receive işlerini tek bir sınıf üzerinden yapması gerekir. Çünkü bugün UART üzerinden gönderdiğiniz mesajı yarın donanım değişikliği ile CAN-BUS üzerinden göndermeniz gerekebilir. Bu yüzden paket oluşturan birimi de ayrı bir sınıf ya da fonksiyon olarak ele alınmalı. Çünkü farklı gereksinimlere göre paket oluşturma biçimi ya da paket içeriği değişebilir. Örneğin açık şekilde gönderdiğimiz paketlerin şifrelenerek gönderilmesi gerektiğinde paket oluşturma biriminin modifiye edilmesi gerekecektir.
Yukarıdaki örneği ele alan uygulamayı yazalım. Harici birim olan motor kontrol kartı ile haberleşerek motor kartının durum bilgisi, motorun pozisyonu ve sıcaklığını alalım.
UML tasarımından da görüleceği gibi her birimin tek bir sorumluluğu var. Eğer uygulamamızda bir sınıf ya da fonksiyonda bir değişiklik için birden fazla noktaya dokunmamız gerekiyor ise Single Responsibility Principle (SRP) – Tek Sorumluluk Prensibine uymamışız demektir. Yukarıdaki UML çizmini bu tanıma göre test edelim.
1- İletişim arayüzü UART’ dan CAN-BUS geçmesi durumunda EXTComm sınıfı etkileniyor mu? Hayır, çünkü bu değişim Comm sınıfı içinde yapılıyor.
2- Paket yapısının değişmesi EXTComm sınıfını etkiliyor mu? Hayır, çünkü paket hazırlanması MsgPacket sınıfı içinde yapılıyor.
3- Motor kartından akım bilgisini almak EXTComm sınıfını etkiliyor mu? Evet, birden fazla noktada değişiklik yapılması gerekiyor mu? Hayır sadece yeni isteği oluşturmamız yeterli.
class IComm { Public: virtual ~IComm(){} virtual int send(void *msg, size_t leng) = 0; virtual int receive(void *msg, size_t *leng) = 0; }; class Comm : public IComm { Public: int send(void *msg, size_t leng) override { std::cout << “Message was sent” << std::endl; return 0; } int receive(void *msg, size_t *leng) override { std::cout << “Message was Received” << std::endl; return 0; } };class IMsgPacket { Public: virtual ~IMsgPacket(void){}; virtual int createPacket(const char *msg, size_t leng, char *buff, size_t *lengPckt) = 0; };class MsgPacket : public IMsgPacket { Public: int createPacket(const char *msg,size_t leng,char *buff,size_t *lengPckt) override { std::cout << “Msg packet was created” << std::endl; return 0; } }; class ExtBoardComm { Public: int askStatus() { char msg[32]; char msgPckt[64]; char msgReceive[32]; size_t leng; //msg içeriği yüklenir MsgPacket msgPacket; Comm comm; msgPacket.createPacket(msg, sizeof(msg), msgPckt, &leng); comm.send(msgPckt, sizeof(msgPckt)); comm.receive(msgReceive, &leng); return 0; } int getEnginePosition() { char msg[32]; char msgPckt[64]; char msgReceive[32]; size_t leng; //msg içeriği yüklenir MsgPacket msgPacket; Comm comm; msgPacket.createPacket(msg, sizeof(msg), msgPckt, &leng); comm.send(msgPckt, sizeof(msgPckt)); comm.receive(msgReceive, &leng); return 666; //return position } int getTemp() { char msg[32]; char msgPckt[64]; char msgReceive[32]; size_t leng; //msg içeriği yüklenir MsgPacket msgPacket; Comm comm; msgPacket.createPacket(msg, sizeof(msg), msgPckt, &leng); comm.send(msgPckt, sizeof(msgPckt)); comm.receive(msgReceive, &leng); return 35; // return temp }; int main (void) { int position; int temp; ExtBoardComm extBoardComm; extBoardComm.askStatus(); position = extBoardComm.getEnginePosition(); std::cout << “Position: ” << position << std::endl; temp = extBoardComm.getTemp(); std::cout << “Temp: ” << temp << std::endl; } |
Diğer bir örneğimizde şu şekilde olsun. Projemizde RS485 hattı üzerinden gelen mesajları SD karta kaydetmemiz gereksin. Bu durumda aşağıdaki gibi bir sınıf oluşturalım ve isteklerimizi receiveData() ve recordData() metodları/fonksiyonları ile gerçekleştirelim. Bu durumda sınıfımız iki işi de yapıyor oldu. Hem mesaj alma işini yapıyor hem de SD karta yazıyor. Bu işleri Comm ve Record sınıfları ile yaparsak işleri ayırmış oluruz. Ayrıca ilerde SD karta değil de yerel bir alana ya da EEPROM kaydetmemiz gerekebilir. Aynı şekilde bugün RS485 için istenen gereksinim yarın CAN-BUS için de istenebilir.
Yukarıdaki tasarım bu hem ileride oluşacak isteklerimizi karşılayacak yapıda hem de tek sorumluluk prensibine de uymaktadır.
#include <iostream> class IComm { Public: virtual ~IComm(){} virtual int receive(void *buff, size_t leng) = 0; }; class Comm : public IComm { Public: int receive(void *buff, size_t leng) override { std::cout << “Data received” << std::endl; return 0; } }; class IRecord { Public: virtual ~IRecord() {}; virtual int write(const void *buff, size_t leng) = 0; }; class Record : public IRecord { Public: int write(const void *buff, size_t leng) override { std::cout << “Data recorded in SD Card” << std::endl; return 0; } }; class CommRecorder { Public: int saveData(void) { char buff[256]; Comm comm; Record record; comm.receive(buff, sizeof(buff)); record.write(buff, sizeof(buff)); return 0; } }; int main (void) { CommRecorder cmRcrd; while(1) { cmRcrd.saveData(); } } |
Yazılım Prensipleri yazı dizisinin tamamını buradan indirebilirsiniz.
Zafer Satılmış
22.10.2019 – Salı