Değişmez nesneleri kullanırken çoğu tavuk ve yumurta problemini çözmek için uygulanabilecek belirli bir tasarım stratejisi var mı?


17

Bir OOP geçmişinden (Java) geliyor, kendi başıma Scala öğreniyorum. Değişken nesneleri tek tek kullanmanın avantajlarını kolayca görebilsem de, birinin böyle bir uygulamanın tamamını nasıl tasarlayabildiğini görmekte zorlanıyorum. Bir örnek vereceğim:

Diyelim ki su ve buz gibi "materyalleri" ve onların özelliklerini temsil eden nesnelerim var (bir oyun tasarlıyorum, bu yüzden gerçekten bu problemim var). Tüm bu malzeme örneklerine sahip bir "yönetici" olurdu. Bir özellik donma ve erime noktası ve malzemenin donma veya erime noktasıdır.

[EDIT] Tüm maddi örnekler "singleton" dur, Java Enum gibi.

0C'de "buz" a donmasını söylemek için "su" ve 1C'de "suya" erir demek için "buz" demek istiyorum. Ancak su ve buz değişmezse, yapıcı parametreleri olarak birbirlerine referans alamazlar, çünkü bunlardan biri önce yaratılmalıdır ve biri henüz mevcut olmayan diğerine yapıcı parametresi olarak referans alamaz. Her ikisini de donma / eritme özellikleri istendiğinde ihtiyaç duydukları diğer malzeme örneğini bulmak için sorgulayabilmeleri için yöneticiye bir referans vererek bunu çözebilirim, ancak daha sonra yönetici arasında aynı sorunu alıyorum ve birbirlerine referansta bulunmaları gereken materyaller, fakat sadece bunlardan biri için yapıcıda sağlanabilir, böylece yönetici veya malzeme değişmez olamaz.

Bu sorun etrafında sadece bir yolu yok, ya da "fonksiyonel" programlama teknikleri ya da çözmek için başka bir desen kullanmam gerekiyor mu?


2
Bana göre, anlattığın gibi, buz da yok. Sadece h2omalzeme var
gnat

1
Bunun daha "bilimsel bir anlam" yaratacağını biliyorum, ama bir oyunda onu bağlama bağlı olarak "değişken" özelliklere sahip bir malzeme yerine "sabit" özelliklere sahip iki farklı malzeme olarak modellemek daha kolay.
Sebastien Diot

Singleton aptalca bir fikir.
DeadMG

@DeadMG Tamam. Bunlar gerçek Java Singletons değildir. Demek istediğim, değişmez oldukları ve birbirine eşit olacağı için her birinin birden fazla örneğini yaratmanın bir anlamı yok. Aslında, gerçek bir statik örneğim olmayacak. "Kök" nesnelerim OSGi hizmetleridir.
Sebastien Diot

Bu diğer sorunun cevabı, değişmezler ile işlerin gerçekten hızlı bir şekilde karmaşıklaştığına dair
şüphemi doğrulıyor

Yanıtlar:


2

Çözüm biraz hile yapmaktır. özellikle:

  • A oluşturun, ancak B referansını başlatılmamış olarak bırakın (B henüz mevcut olmadığından).

  • B'yi oluşturun ve A'yı işaret etmesini sağlayın.

  • A'yı B'yi işaret edecek şekilde güncelleyin. Bundan sonra A veya B'yi güncellemeyin.

Bu açıkça yapılabilir (örnek C ++ ile):

struct List {
    int n;
    List *next;

    List(int n, List *next)
        : n(n), next(next);
};

// Return a list containing [0,1,0,1,...].
List *binary(void)
{
    List *a = new List(0, NULL);
    List *b = new List(1, a);
    a->next = b; // Evil, but necessary.
    return a;
}

veya dolaylı olarak (Haskell'de örnek):

binary :: [Int]
binary = a where
    a = 0 : b
    b = 1 : a

Haskell örneği, karşılıklı bağımlı değişmez değerler yanılsamasını elde etmek için tembel değerlendirme kullanır. Değerler şu şekilde başlar:

a = 0 : <thunk>
b = 1 : a

ave bher ikisi de bağımsız olarak geçerli baş-normal formlarıdır . Her eksileri, diğer değişkenin nihai değerine ihtiyaç duyulmadan oluşturulabilir. Gövde değerlendirildiğinde, aynı veriyi bişaret eder.

Bu nedenle, iki değişmez değerin birbirine işaret etmesini istiyorsanız, ikincisini oluşturduktan sonra ilkini güncellemeniz veya bunu yapmak için daha üst düzey bir mekanizma kullanmanız gerekir.


Özel örneğinizde, Haskell'de şöyle ifade edebilirim:

data Material = Water {temperature :: Double}
              | Ice   {temperature :: Double}

setTemperature :: Double -> Material -> Material
setTemperature newTemp (Water _) | newTemp <= 0.0 = Ice newTemp
                                 | otherwise      = Water newTemp
setTemperature newTemp (Ice _)   | newTemp >= 1.0 = Water newTemp
                                 | otherwise      = Ice newTemp

Ancak, bu konuya bir adım atıyorum. Her kurucunun sonucuna bir setTemperatureyöntemin eklendiği nesne yönelimli bir yaklaşımda, Materialkurucuları birbirine yöneltmeniz gerektiğini düşünürdüm. Yapıcılara değişmez değerler olarak davranılırsa, yukarıda özetlenen yaklaşımı kullanabilirsiniz.


(Haskell'in sözdizimini anladığımı varsayarak) Mevcut çözümümün gerçekten çok benzer olduğunu düşünüyorum, ancak bunun "doğru olan" olup olmadığını veya daha iyi bir şeyin olup olmadığını merak ediyordum. Önce her (henüz oluşturulmamış) nesne için bir "tanıtıcı" (başvuru) oluşturuyorum, sonra tüm nesneleri yaratıyorum, onlara ihtiyaç duydukları tutamaçları veriyorum ve son olarak tanıtıcıyı nesnelere başlatırım. Nesnelerin kendileri değişmez, ancak tutamaklar değil.
Sebastien Diot

6

Örneğinizde, geçerli nesneyi değiştirmeye çalışmak yerine ApplyTransform()döndüren bir yöntem gibi bir şey kullanmak için bir nesneye dönüşüm uyguluyorsunuz BlockBase.

Örneğin, bir ısı bloğu uygulayarak bir IceBlock'u bir WaterBlock'a değiştirmek için,

BlockBase currentBlock = new IceBlock();
currentBlock = currentBlock.ApplyTemperature(1); 
// currentBlock is now a WaterBlock 

ve IceBlock.ApplyTemperature()yöntem şöyle görünecektir:

public class IceBlock() : BlockBase
{
    public BlockBase ApplyTemperature(int temp)
    {
        return (temp > 0 ? new WaterBlock((BlockBase)this) : this);
    }
}

Bu iyi bir cevap, ama ne yazık ki sadece benim "malzemeler", aslında "bloklarım" hepsi singleton, çünkü yeni WaterBlock () sadece bir seçenek değildir söz başarısız çünkü. Bu değişmez olanın ana yararıdır, onları sonsuza kadar tekrar kullanabilirsiniz. Koç içinde 500.000 blok yerine 100 blok için 500.000 referansım var. Çok daha ucuz!
Sebastien Diot

Peki BlockList.WaterBlockyeni bir blok oluşturmak yerine geri dönmeye ne dersiniz ?
Rachel

Evet, yaptığım şey bu, ancak engelleme listesini nasıl alabilirim? Açıkçası, bloklar blok listesinden önce oluşturulmalıdır ve bu nedenle blok gerçekten değişmezse, blok listesini parametre olarak alamaz. Peki listeyi nereden alıyor? Genel olarak, kodu daha kıvrımlı hale getirerek, tavuk ve yumurta problemini bir seviyede çözersiniz, sadece bir sonraki aşamada tekrar elde etmek için. Temel olarak, değişmezliğe dayalı bir uygulama oluşturmanın hiçbir yolunu göremiyorum. Sadece "küçük nesneler" için uygulanabilir, kaplar için geçerli değildir.
Sebastien Diot

@Sebastien Bence bu BlockListsadece staticher bloğun tekli örneklerinden sorumlu bir sınıftır, bu yüzden BlockList(C # 'a alışkınım)
Rachel

@Sebastien: Singeltons kullanıyorsanız fiyatı ödersiniz.
DeadMG

6

Döngüyü kırmanın bir başka yolu, malzeme ve dönüşüm kaygılarını, bazı telafi dillerinde ayırmaktır:

water = new Block("water");
ice = new Block("ice");

transitions = new Transitions([
    new transitions.temperature.Below(0.0, water, ice),
    new transitions.temperature.Above(0.0, ice, water),
]);

İlk başta bunu okumak benim için zordu, ama bence bu aslında savunduğum yaklaşımla aynı.
Aidan Cully

1

İşlevsel bir dil kullanacaksanız ve değişmezliğin faydalarını gerçekleştirmek istiyorsanız, o zaman soruna akılda yaklaşmalısınız. Bir dizi sıcaklığı destekleyebilen bir nesne türü "buz" veya "su" tanımlamaya çalışıyorsunuz - değişmezliği desteklemek için, sıcaklık her değiştiğinde yeni bir nesne oluşturmanız gerekir, bu da israftır. Bu yüzden blok tipi ve sıcaklık kavramlarını daha bağımsız hale getirmeye çalışın. Scala'yı bilmiyorum (benim öğrenme listemde :-)), ama Joey Adams'tan borçlanma Haskell'den cevap , şöyle bir şey öneriyorum:

data Material = Water | Ice

blockForTemperature :: Double -> Material
blockForTemperature x = 
  if x < 0 then Ice else Water

ya da belki:

transitionForTemperature :: Material -> Double -> Material
transitionForTemperature oldMaterial newTemp = 
  case (oldMaterial, newTemp) of
    (Ice, _) | newTemp > 0 -> Water
    (Water, _) | newTemp <= 0 -> Ice

(Not: Bunu çalıştırmayı denemedim ve Haskell'in biraz paslı.) Şimdi, geçiş mantığı malzeme türünden ayrıldı, bu yüzden çok fazla bellek harcamıyor ve (bence) oldukça biraz daha işlevsel odaklı.


Aslında "fonksiyonel dili" kullanmaya çalışmıyorum çünkü ben anlamadım! Normalde önemsiz olmayan herhangi bir işlevsel programlama örneğinden sakladığım tek şey: "Lanet olsun, ben daha zekiydim!" Bunun ötesinde, bunun herkes için nasıl bir anlamı olabilir. Öğrencilerimin günlerinden beri, Prolog'un (mantık tabanlı), Occam'ın (varsayılan olarak her şey paralel olarak koşuyor) ve hatta toplayıcının mantıklı olduğunu hatırlıyorum, ancak Lisp sadece çılgınca şeylerdi. Ama ben "devlet" dışında bir "durum değişikliği" neden kodu hareket noktası olsun.
Sebastien Diot
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.