| |
| Posted by Ali Çehreli in reply to Salih Dincer | PermalinkReply |
|
Ali Çehreli
Posted in reply to Salih Dincer
| On 12/15/21 10:37 PM, Salih Dincer wrote:
> On Wednesday, 15 December 2021 at 14:39:39 UTC, Ali Çehreli wrote:
>> On 12/14/21 11:40 PM, Salih Dincer wrote:
>>
>> > class SesliAlet {
>> > string telefonSesi;
>> > string saatSesi;
>>
>> Orada OOP'ye aykırı bir durum farkediyorum
>
> Ohh tamam şimdi hemen internetten bulduğum klasik örneğe geçiyorum.
[...]
> ```d
> abstract class Animal {
> private string status;
>
> this() {
> this.status = "idle";
> }
>
> string get() {
> return this.status;
> }
>
> void set(string idle) {
> this.status = idle;
> }
Event, Animal sıradüzeni klasiktir ama nerede get+set, orada çokluk! :p Tür niteliklerinin get+set ile erişildiği durumlar nadirdir çünkü çoğunlukla set() yerine bir üye işlevin içindeki bir yan etki kullanılır:
void uyu() {
// ...
this.status = "idle";
}
Hele buradaki gibi üyeye doğrudan atama yapılacaksa neden public bir üye değişken olmasın?
abstract class Animal {
public string status;
// ...
}
Hiçbir şey; çünkü atanan değerle ilgili bir kısıtlama gerektiğinde bunları şöyle gerçekleştirebiliriz:
abstract class Animal {
string status() const {
// status'u belirleyen her ne ise...
// Belki de adı status_ olan bir üye değişken
}
}
Durumumumz internetteki örnekten çok daha iyi çünkü körlemesine set() eşdeğeri sağlamadık. Gerekirse:
abstract class Animal {
void status(string değer) {
// Durumu değiştirmek için değer'i kullanırız
}
}
Bunlara rağmen, hem kendi deneyimlerim doğrultusunda hem de okuduklarıma dayanarak, üst sınıfta üye değişken çok nadiren bulunmalıdır. Ben olsam şöyle yapardım:
abstract class Animal {
void uyu();
void sesÇıkart();
// vs.
}
Yani, üst sınıfta yalnızca davranış bulunmalı. Ve eğer öyle olacaksa, üst sınıf yerine bir interface kullanılmalı:
interface Animal {
void uyu();
void sesÇıkart();
// vs.
}
Gerçekten ortak değişken varsa, o değişkenler bir ara sınıfta bulunmalı. Örneğin:
class DurumluAnimal : Animal {
string status;
}
class Cat : DurumluAnimal {
// ...
}
(Not: Yazım hatalarım olabilir. Hatta, bu durumda status'un protected olması da iyi olabilir, vs.)
Bütün bunların üstüne, OOP'nin, baştan verdiği sözlerle karşılaştırıldığında çok başarısız olmuş bir çözüm olduğunun altını çizmekte yarar var. Kendi adıma, OOP yalnızca son gösterdiğim basit sıradüzenler kullanıldığında işe yarıyor. O basitlikten uzaklaşıldıkça içinden çıkılmaz bir hal alıyor. Sonuçta Java ve D gibi diller çoklu sınıf kalıtımını yasaklıyorlar, vs.
İlgili olarak, bu gibi örneklerde gördüğümüz OOP'nin pek işe yaramadığını şöyle bir örnekle gösterebiliriz: Borsa (Monopoly) oyununu sınıflarla tasarladığımızı düşünelim. Oyun, Oyuncu, Sokak, Ev, Otel, vs. gibi sınıflar düşünebiliriz... Peki, "bu oyuncu bu sokağı alabilir" kararı hangi sınıfın nasıl bir üye işlevine ait olmalı?
Bu soruyla ilginç geliyorsa "anemic design model" ve "rich design model" karşılaştırmalarına bakarak kendi kararınızı verebilirsiniz.
Her ne kadar OOP tanrıları "anemic design model"a karşı olsalar da, kendim Borsa örneğine bakarak "anemic design model"ın üstün olduğunda karar kılmıştım. Ama dediğim gibi, artık "şu pattern", "bu pattern", "şu model", vs. gibi mantıklara bakmadan basit tasarımlar bulmaya çalışıyorum. Kendi koduma baktığımda 'class'ın çok az geçtiğini görüyorum.
D'nin de aralarında bulunduğu bütün yaygın programlama dillerinin karar kıldığı OOP yönteminin karşıtı olarak hep tekrarladığım bir örnek de "Open Methods"dır:
https://dconf.org/2018/talks/leroy.html
Hem o sunumu hem de "Open Methods"ın gerçekleştirilmesini çok etkileyici bulmuşumdur.
> Dlang Tour'da bile değişik bir
> [örnek](https://tour.dlang.org/tour/en/basics/classes) sunulmuşken...
O örnek, çalışma zamanında her türden değer tutabilen bir Any türünü gösteriyor. Öyle bir sınıfta tutulmak olan türün ne olduğunun gösterilmesi kabul edilebilir. Üstelik, string yerine Phobos'un Variant'ının yaptığı gibi TypeInfo da döndürülebilir:
https://dlang.org/phobos/std_variant.html#.VariantN.type
(Ne yazık ki, std.variant çoğu başka Phobos modülüne göre fazla karmaşık. Hatta, Variant bile VariantN gibi bir türün alias'ı olarak tanımlanmış! Yeni başlayanlara kolay gelsin. :) )
DLang Tour'daki örneği kendimce geliştirdim. Özellikle kod tekrarını azaltmaya çalıştım:
import std.stdio : writeln;
// Bunu 'interface' yapmak isterdim ama main içinde
// örneğin Integer'ın Any'ye otomatik olarak dönüşemedeğini
// görüyorum. Galiba bu ya bir hata ya da D'den beklenen yeni bir
// olanak. O yüzden 'class' seçiyorum ve daha önceden önerdiğim
// "en yukarıda interface olsun" önerimi geri çekiyorum.
class Any {
abstract string getType();
abstract string convertToString();
}
// Bu sınıf getType'ı hallediyor.
// Not: Burada asıl programdan ayrılıp "integer"
// yerine "int" döndürüyorum. Düzeltilebilir ama
// bence sorun değil.
class AnyImpl(T) : Any {
override string getType() {
return typeid(T).toString;
}
}
// Bu, değer taşıyan türleri temsil ediyor.
class AnyValue(T, string formatStr = "%s") : AnyImpl!T {
T value;
this(T value) {
this.value = value;
}
override string convertToString() {
import std.format : format;
return format!formatStr(value);
}
}
// Bunları yazmak artık çok kolay:
alias Integer = AnyValue!int;
alias Float = AnyValue!(float, "%.1f");
void main()
{
Any[] anys = [
new Integer(10),
new Float(3.1415f)
];
foreach (any; anys) {
writeln("any's type = ", any.getType());
writeln("Content = ",
any.convertToString());
}
}
Ali
|