OOP'ta dairesel referanslara ihtiyaç duyan bu gerçek dünya aktivitesini modellemenin doğru yolu nedir?


24

Java projesinde dairesel referanslarla ilgili bir sorunla güreşiyorum. Söz konusu nesnelerin birbirine bağımlı olduğu ve birbirleri hakkında bilgi sahibi olması gereken bir gerçek dünya durumunu modellemeye çalışıyorum.

Proje, masa oyunu oynamanın genel bir modelidir. Temel sınıflar spesifik değildir, ancak satranç, tavla ve diğer oyunların spesifikasyonlarıyla başa çıkmak için genişletilmiştir. Bunu 11 yıl önce yarım düzine farklı oyunla bir applet olarak kodladım, ancak sorun bunun dairesel referanslarla dolu olması. İç içe geçmiş tüm sınıfları tek bir kaynak dosyasında doldurarak uyguladım, ancak bunun Java'da kötü olduğu fikrine kapılıyorum. Şimdi bir Android uygulamasıyla benzer bir şey uygulamak istiyorum ve işleri doğru bir şekilde yapmak istiyorum.

Sınıflar:

  • Kural Kitabı: Kurulun başlangıç ​​düzeni, ilk hamle yapanlar, mevcut hamle, önerilen hamleden sonra oyun devletine ne olduğu gibi, ilk hamle durumu gibi şeyler için sorgulanabilen bir nesne mevcut veya önerilen bir kurul pozisyonu.

  • Tahta: Bir Oyun Tahtasını, bir Hareketi yansıtması talimatı verilen basit bir gösterimidir.

  • MoveList: bir Hareketler listesi. Bu çift amaçlıdır: belirli bir noktada mevcut olan hamleler veya oyunda yapılan hamlelerin bir seçimi. İki yakın benzer sınıfa ayrılabilir, ancak bu sorduğum soru ile ilgili değil ve daha da karmaşıklaştırabilir.

  • Taşı: tek bir hamle. Atomun bir listesi olarak hareketle ilgili her şeyi içerir: buradan bir parça alın, aşağıya koyun, yakalanan parçayı oradan çıkarın.

  • Devlet: Devam eden bir oyunun tam durum bilgisi. Yalnızca Yönetim Kurulu pozisyonu değil, bir MoveList ve şimdi kimlerin hareket edeceği gibi diğer devlet bilgileri. Satrançta, her oyuncunun kralı ve taşlarının hareket edilip edilmediği kaydedilmiştir.

Örneğin, dairesel referanslar boldur: RuleBook, belirli bir zamanda hangi hamlelerin mevcut olduğunu belirlemek için oyun Durumu hakkında bilgi sahibi olmak zorundadır, ancak oyun Devleti, RuleBook'u başlangıçtaki başlangıç ​​düzeni ve bir hamleye eşlik eden yan etkiler için sorgulamalıdır. yapılır (örneğin, sonraki hareket eden).

Yeni sınıf kümesini hiyerarşik olarak düzenlemeye çalıştım, her şey hakkında bilmesi gereken en üstte RuleBook ile. Ancak bu, çok sayıda yöntemi RuleBook sınıfına (örneğin, bir hamle yapmak gibi) taşımak zorunda kalmasıyla sonuçlanır, böylece monolitik hale gelir ve bir RuleBook'un ne olması gerektiğini özellikle temsil etmez.

Peki bunu organize etmenin doğru yolu nedir? Gerçek dünya oyununu doğru şekilde modelleme girişiminden vazgeçerek dairesel referanslardan kaçınmak için RuleBook'u BigClassThatDoesAlmostEherthingInTheGame'e dönüştürmeli miyim? Yoksa birbirine bağlı sınıflara bağlı kalmalı ve gerçek dünya modelimi koruyarak derleyiciyi bir şekilde derlemesini sağlayacak mıyım? Yoksa eksik olan bazı belirgin geçerli bir yapı var mı?

Verebileceğiniz her türlü yardım için teşekkürler!


7
Peki ya RuleBookmesela Statetartışmayı kabul edip, geçerliyse MoveList, örneğin "şimdi buradayız, daha sonra ne yapılabilir?"
jonrsharpe

@ Jonrsharpe ne dedi. Gerçek bir masa oyunu oynarken kural kitabı da oynanan gerçek oyunlar hakkında hiçbir şey bilmiyor. Muhtemelen hareketleri hesaplamak için başka bir sınıf daha tanıtacağım ama bu RuleBook sınıfının ne kadar büyük olduğuna bağlı olabilir.
Sebastiaan van den Broek

4
Tanrı nesnesinden kaçınmak (BigClassThatDoesAlmostEverythingInTheGame), dairesel referanslardan kaçınmaktan çok daha önemlidir.
user281377

2
@ user281377, mutlaka karşılıklı olarak özel hedefler değiller!
jonrsharpe

1
Modelleme girişimlerini gösterebilir misiniz? Örneğin bir diyagram?
Kullanıcı

Yanıtlar:


47

Java projesinde dairesel referanslarla ilgili bir sorunla güreşiyorum.

Java'nın çöp toplayıcısı referans sayma tekniklerine dayanmamaktadır. Dairesel referanslar, Java'da herhangi bir soruna neden olmaz. Java'da mükemmel doğal dairesel referansları ortadan kaldırarak harcanan zaman boşa harcanır.

Bunu kodladım [...] ama sorun bunun dairesel referanslarla dolu olması. İç içe geçmiş tüm sınıfları tek bir kaynak dosyasında doldurmak suretiyle uygulamaya koydum , [...]

Gerekli değil. Tüm kaynak dosyaları bir kerede derlerseniz (örneğin, javac *.java), derleyici tüm ileri referansları sorunsuz olarak çözer.

Ya da birbirine bağımlı sınıflara bağlı kalmalı mıyım ve derleyiciyi bir şekilde derlemesine koaksiyel yapmalı mıyım, [...]

Evet. Uygulama sınıflarının birbirine bağımlı olması beklenmektedir. Aynı pakete ait olan tüm Java kaynak dosyalarını bir kerede derlemek akıllıca bir kesmek değildir, tam olarak Java'nın çalışması gerektiği şekildedir .


24
"Dairesel referanslar, Java'da herhangi bir soruna neden olmaz." Derleme açısından bu doğrudur. Dairesel referanslar, kötü tasarım olarak kabul edilir .
doğrayın

22
Dairesel referanslar birçok durumda tamamen doğaldır, bu yüzden Java ve diğer modern dillerin basit bir referans sayacı yerine karmaşık bir çöp toplayıcı kullanması bu yüzdendir.
user281377

3
Java, dairesel referansları çözebilmek harika bir şey ve birçok durumda doğal oldukları kesinlikle doğru. Ancak OP, özel bir durum sundu ve bu dikkate alınması gereken bir durumdu. Karışık spagetti kodu muhtemelen bu sorunu çözmenin en iyi yolu değildir.
Matthew

3
Lütfen ilgisiz programlama dilleri hakkındaki doğrulanmamış FUD bilgisini yaymayın. Python, çağlardan beri referans devirlerin GC'sini destekledi ( ayrıca SO: burada ve burada da docs ).
Christian Aichinger

2
IMHO bu cevap sadece vasat, çünkü örneğin OP için yararlı olan dairesel referanslar hakkında tek bir kelime yok.
Doktor Brown

22

Verilen, dairesel bağımlılıklar tasarım açısından sorgulanabilir bir pratiktir, ancak yasak değildir ve tamamen teknik bir bakış açısına göre mutlaka sorunlu değillerdir; Çoğu senaryoda, bazı durumlarda kaçınılmazdır ve bazı nadir durumlarda bile olması gereken yararlı bir şey olarak kabul edilebilir.

Aslında, java derleyicisinin dairesel bir bağımlılığı inkar edeceği çok az senaryo vardır. (Not: daha fazlası olabilir, şu anda sadece aşağıdakileri düşünebilirim.)

  1. Kalıtım: A sınıfını uzatan A sınıfı B sınıfına sahip olamazsınız, ve bunun alternatifi mantıklı bir bakış açısıyla kesinlikle bir anlam ifade etmeyeceğinden, buna sahip olamamanız tamamen mantıklıdır.

  2. Yöntem-yerel sınıflar arasında: bir yöntemde bildirilen sınıflar birbirlerine dairesel olarak referans göstermeyebilir. Bu muhtemelen java derleyicisinin sınırlandırmasından başka bir şey değildir, çünkü muhtemelen böyle bir şeyi yapabilme yeteneği, onu desteklemek için derleyiciye gitmek zorunda kalacak ek karmaşıklığı haklı çıkarmak için yeterli değildir. (Java programcılarının çoğu, bir yöntemi içeren bir sınıfı tanımlayabildiğiniz, çoklu sınıfları tanımlayabilecekleri ve bu sınıfları birbirlerine dairesel olarak referans gösterebileceklerinden bile haberdar değillerdir.)

Bu nedenle, dairesel bağımlılıkları en aza indirme arayışının teknik doğruluk arayışı değil, tasarım saflığı arayışı olduğu gerçeğinin farkına varılması ve çıkarılması önemlidir.

Bildiğim kadarıyla, döngüsel bağımlılıkları ortadan kaldırmak için hiçbir indirgemeci yaklaşım bulunmuyor, bunun anlamı, döngüsel referanslara sahip bir sistemi almak, birbiri ardına uygulamak ve sona erdirmek için basit önceden belirlenmiş "beyinsiz" adımlardan ibaret olmayan hiçbir tarifin olmadığıdır. dairesel referanssız bir sistemle. Fikrinizi çalışmaya koymanız ve tasarımınızın doğasına bağlı yeniden düzenleme adımları uygulamanız gerekir.

Elinizdeki özel durumda, bana ihtiyacınız olan şeyin, belki de diğer tüm varlıkları bilen (bilmeden, diğer varlıklar olmadan) "Oyun" veya "GameLogic" olarak adlandırılan yeni bir varlık olduğu anlaşılıyor. ) böylece diğer varlıkların birbirlerini tanımaları gerekmez.

Örneğin, RuleBook varlığınızın GameState varlığına ilişkin herhangi bir şey bilmesi gerektiğine mantıklı gelmiyor, çünkü bir kural kitabı oynamak için danıştığımız bir şeydir, oynamak için aktif rol alan bir şey değildir. Bu nedenle, hangi hareketlerin uygun olduğunu belirlemek için hem kural kitabına hem de oyun durumuna bakması gereken bu yeni "Oyun" varlığıdır ve bu döngüsel bağımlılıkları ortadan kaldırır.

Şimdi, bu yaklaşımla sorununun ne olacağını tahmin edebileceğimi düşünüyorum: “Oyun” varlığını bir oyun-agnostik şekilde kodlamak çok zor olacak, bu yüzden muhtemelen sadece bir değil iki ile sonuçlanacak Her farklı oyun türü için ısmarlama uygulamalara sahip olması gereken varlıklar: "Kural Kitabı" ve "Oyun" varlığı. Hangi sırayla bir "RuleBook" varlık ilk etapta sahip olma amacını yener. Bunun hakkında söyleyebileceğim tek şey, belki de, belki de, belki de, birçok farklı türde oyunu oynayabilecek bir sistem yazma özleminin asil, belki de hasta gibiydi. Ayakkabının içinde olsaydım, bütün farklı oyunların durumunu göstermek için ortak bir mekanizma ve tüm bu oyunlar için kullanıcı girişi almak için ortak bir mekanizma kullanmaya odaklanırdım,


1
Sağol Mike. Oyun varlıklarının sakıncaları konusunda haklısın; eski uygulama kodu ile yeni bir RuleBook alt sınıfından ve uygun grafik tasarımdan biraz daha fazlasını içeren yeni oyunlar çizebildim.
Damian Walker

10

Oyun teorisi, oyunları önceki hamlelerin (onları kimin oynadığı dahil değer türlerinin) bir listesi ve ValidMoves (öncekiHareketli) bir işlev olarak görür.

Oyunun UI olmayan kısmı için bu modeli izlemeye ve takip etmeye çalışacağım ve tahta gibi hareketlere hamle gibi davranacağım.

UI daha sonra mantığa tek yönlü ref ile standart OO şeyler olabilir


Yorumları yoğunlaştırmak için güncelleme

Satranç düşünün. Satranç oyunları genellikle hamle listeleri olarak kaydedilir. http://en.wikipedia.org/wiki/Portable_Game_Notation

Hamle listesi, oyunun tam halini tahta resminden çok daha iyi tanımlar.

Örneğin, Board, Piece, Move vb. Nesneler yapmaya başlıyoruz ve Piece.GetValidMoves () gibi metodlar kullanıyoruz.

ilk önce tahtaya atıfta bulunmak zorunda olduğumuzu görüyoruz, ama sonra alçıya almayı düşünüyoruz. Sadece kralınızı veya kalenizi henüz hareket ettirmediyseniz bunu yapabilirsiniz. Bu yüzden kralın ve kalenin üzerindeki bir MovedAlready bayrağına ihtiyacımız var. Benzer şekilde piyonlar ilk karelerinde 2 kareyi hareket ettirebilirler.

Öyleyse, oyuncu seçiminde kralın geçerli hamlesinin kalenin varlığına ve durumuna bağlı olduğunu görüyoruz, bu nedenle tahtanın üzerinde parçalar olması ve bu parçaları referans alması gerekiyor. döngüsel ref probleminize giriyoruz.

Ancak, Move'u değişmez bir yapı ve oyun durumu olarak önceki hareketlerin listesi olarak tanımlarsak, bu sorunların ortadan kalktığını görürüz. Dökümün geçerli olup olmadığını görmek için kale ve kral hamlelerinin varlığının hamle listesini kontrol edebiliriz. Piyonun geçip geçemeyeceğini görmek için, diğer piyonun hareket halindeyken çift hareket yapıp yapmadığını kontrol edebiliriz. Kurallar dışında hiçbir referans gerekmez -> Taşı

Şimdi satrançta statik bir tahta var ve peices her zaman aynı şekilde kuruluyor. Ancak, alternatif bir kuruluma izin verdiğimiz bir değişkenimiz olduğunu varsayalım. belki bazı parçaları bir engel olarak görmezden gelmek.

Kur hareketlerini, 'kutudan kareye X'e kadar' hamle olarak eklersek ve bu hamleyi anlamak için Kurallar nesnesine uyursak, o zaman oyunu bir hamle dizisi olarak gösterebiliriz.

Benzer şekilde oyununuzda tahtanın kendisi statik değilse, satrançta kareler ekleyebileceğimizi veya kareleri tahtadan çıkarabileceğimizi ve böylece hareket ettirilemeyeceklerini söyleyin. Bu değişiklikler, Kurallar motorunuzun genel yapısını değiştirmeden veya benzer bir BoardSetup nesnesine başvurmak zorunda kalmadan Hareketler olarak da gösterilebilir .


Bu, mantığınızı yavaşlatan ValidMoves'in uygulanmasını zorlaştıracaktır.
Taemyr

pek değil, pano ayarının değişken olduğunu varsayıyorum, bu yüzden bir şekilde tanımlamanız gerekiyor. Kurulumu dönüştürürseniz, hesaplamaya yardımcı olmak için başka bir yapıya veya nesneye taşırsanız, gerekirse sonucu önbelleğe alabilirsiniz. Bazı oyunlarda oyunla değişen tahtalar vardır ve bazı geçerli hamleler mevcut konumdan ziyade önceki hareketlere bağlı olabilir (örn. Satrançta döküm)
Ewan

1
Bayraklar ve eşyalar eklemek, sadece geçmişe sahip olmaktan kaçındığınız karmaşıklıktır. üzerinden geçmek için pahalı değil geçerli tahta kurulumunu almak için 100 satranç hamlesi demek ve hamle arasındaki sonucu önbelleğe alabilirsiniz
Ewan

1
ayrıca nesne modelinizi kuralları yansıtacak şekilde değiştirmekten de kaçınırsınız. yani satranç için, eğer geçerliMoves -> Piece + Board yaparsanız, oyuncu adayı, pasif, ilk önce piyonlar ve parça tanıtımı için hareket edemezsiniz ve nesnelere fazladan bilgi eklemek veya üçüncü bir nesneye atıf yapmak zorunda kalırsınız. Ayrıca kimin gittiği fikrini ve keşfedilen çek gibi kavramları da kaybedersiniz
Ewan

1
@Gabe Bu boardLayout, hepsinin bir işlevidir priorMoves(yani, bunu devlet olarak sürdürürsek, her şeyden başka hiçbir şey katkıda bulunmaz thisMove). Bu nedenle Ewan'ın önerisi esasen “orta erkeği kesmek” tir - geçerli olan hareketler önceki tümünün doğrudan işlevini yerine getirir validMoves( boardLayout( priorMoves ) ).
OJFord

8

Nesne yönelimli programlamada iki sınıf arasında dairesel bir referansın çıkarılmasının standart yolu, daha sonra bunlardan biri tarafından uygulanabilecek bir arayüz sunmaktır. Yani, sizin durumunuzda, o zaman bir (yani tarafından uygulanan bir arayüz olurdu) RuleBookatıfta bulunarak atıfta bulunabilirsiniz . Bu aynı zamanda testi kolaylaştırır, çünkü daha sonra test amacıyla farklı (muhtemelen daha basit) bir başlangıç ​​konumu kullanan bir tane oluşturabilirsiniz .StateInitialPositionProviderRuleBookState


6

Davaınızdaki dairesel referansların ve tanrı nesnesinin, oyun akışının kontrolünü durumdan ve oyunun kural modellerinden ayırarak kolayca kaldırılabileceğine inanıyorum. Bunu yaparak, muhtemelen çok fazla esneklik kazanacak ve gereksiz bir karmaşıklıktan kurtulacaksınız.

Bence oyun akışını kontrol eden ve kural kitabına ya da oyun durumuna bu sorumluluğu vermek yerine gerçek durum değişikliklerini yapan bir kontrolöre ("bir oyun ustası") sahip olmanız gerektiğini düşünüyorum.

Bir oyun durumu nesnesinin kendisini değiştirmesi veya kuralların farkında olması gerekmez. Sınıfın yalnızca uygulamanın geri kalanı için kolayca kullanılabilecek (oluşturulmuş, denetlenmiş, değiştirilmiş, ısrarlı, kaydedilmiş, kopyalanmış, önbelleklenmiş vb.) Ve verimli oyun durumu nesneleri modeli sağlaması gerekir.

Kural kitabının devam eden herhangi bir oyunda bilmesi ya da oynaması gerekmez. Hangi hamlenin yasal olduğunu söyleyebilmek için yalnızca bir oyun durumuna bakmalı ve oyun durumuna bir hamle uygulandığında ne olacağını sorduğunda sadece ortaya çıkan bir oyun durumu ile cevap vermelidir. Başlangıç ​​düzeni istendiğinde bir başlangıç ​​oyun durumu da sağlayabilir.

Kontrolör, oyun durumları ve kural kitabı ve belki de oyun modelinin diğer bazı nesnelerinin farkında olmalıdır, ancak ayrıntılarla uğraşması gerekmez.


4
Kesinlikle benim düşüncem. OP aynı sınıflarda çok fazla veri ve prosedür karıştırıyor . Bunları daha fazla ayırmak daha iyi. Bu konuyla ilgili iyi bir konuşma. BTW, "oyun durumuna bakış" okuduğumda, "işlev için argüman" olduğunu düşünüyorum. Yapabilseydim +100.
jpmc26

5

Bence buradaki problem, hangi görevlerin hangi sınıflar tarafından yerine getirileceğinin net bir tanımını vermemiş olmanızdır. Her sınıfın ne yapması gerektiğinin iyi bir açıklaması olduğunu düşündüğümü anlatacağım, sonra fikirleri gösteren bir genel kod örneği vereceğim. Kodun daha az eşlendiğini göreceğiz ve bu nedenle döngüsel referansları yoktur.

Her sınıfın ne yaptığını tarif etmeye başlayalım.

GameStateSınıf yalnızca oyunun mevcut durumu hakkında bilgi içermelidir. Oyunun geçmiş durumları ya da gelecekteki hareketlerin mümkün olabileceği hakkında herhangi bir bilgi içermemelidir. Sadece satrançta hangi karelerde ne olduğu, ya da tavlada hangi noktalarda ne kadar ve hangi tür dama olduğu hakkında bilgi içermelidir. Bu GameStateirade, satrançta oyun oynama ya da tavlada ikişer küp hakkında bilgi gibi, bazı ekstra bilgiler içermelidir.

MoveSınıf biraz zordur. Hareketi oynamanın sonucunu belirterek oynayacak bir hamle belirleyebileceğimi söyleyebilirim GameState. Böylece bir hareketin sadece bir olarak uygulanabileceğini hayal edebilirsiniz GameState. Ancak, (örneğin), hareket halindeyken tahtada tek bir nokta belirterek bir hareketi belirtmenin daha kolay olduğunu hayal edebilirsiniz. MoveSınıfımızın her ikisini de davalara taşıyacak kadar esnek olmasını istiyoruz . Bu nedenle, Movesınıf aslında bir hamle öncesi GameStateve yeni bir hamle sonrası dönen bir yöntem ile bir arayüz olacak GameState.

Şimdi RuleBooksınıf kuralları hakkında her şeyi bilmek sorumludur. Bu üç şeye ayrılabilir. İlk olanın ne GameStateolduğunu bilmesi, hangi hamlenin yasal olduğunu bilmesi ve oyunculardan birinin kazanıp kazanmadığını anlayabilmesi gerekiyor.

Ayrıca GameHistory, yapılan tüm hareketleri ve GameStatesolanları takip etmek için bir sınıf oluşturabilirsiniz . Yeni bir sınıf gereklidir, çünkü bir GameStatekişinin GameStatekendisinden önce gelenleri bilmekle sorumlu olmayacağına karar verdik .

Bu tartışacağım sınıfları / arayüzleri sonlandırıyor. Ayrıca bir Boardsınıfın var. Ancak farklı oyunlardaki tahtaların yeterince farklı olduğunu düşünüyorum; tahtalarla ne yapılabileceğini görmek zor. Şimdi genel arayüzler vermeye ve genel sınıfları uygulamaya devam edeceğim.

İlk GameState. Bu sınıf tamamen oyuna bağlı olduğundan, genel bir Gamestatearayüz veya sınıf yoktur.

Sonraki olduğunu Move. Dediğim gibi, bu, hareket öncesi durumu alan ve hareket sonrası durumu üreten tek bir metoda sahip bir arayüz ile gösterilebilir. İşte bu arayüzün kodu:

package boardgame;

/**
 *
 * @param <T> The type of GameState
 */
public interface Move<T> {

    T makeResultingState(T preMoveState) throws IllegalArgumentException;

}

Bir tür parametresi olduğuna dikkat edin. Bunun nedeni, örneğin, ChessMovehamle öncesi işlemin özellikleri hakkında bir bilgiye ihtiyaç duyulması gerektiğidir ChessGameState. Yani, örneğin, sınıf beyanı ChessMoveolacaktır

class ChessMove extends Move<ChessGameState>,

zaten bir ChessGameStatesınıf tanımladığınız yer .

Sonra genel RuleBooksınıfı tartışacağım . İşte kod:

package boardgame;

import java.util.List;

/**
 *
 * @param <T> The type of GameState
 */
public interface RuleBook<T> {

    T makeInitialState();

    List<Move<T>> makeMoveList(T gameState);

    StateEvaluation evaluateState(T gameState);

    boolean isMoveLegal(Move<T> move, T currentState);

}

Yine GameStatesınıf için bir type parametresi var . Yana RuleBookbaşlangıç durumu ne olduğunu bilmek gerekiyordu, biz başlangıç durumunu vermek için bir yöntem koyduk. Yana RuleBookhamle yasal olduğunu bilmek gerekiyordu bir hareket belli bir durumda yasal ve belirli bir devlet için yasal hamle listesini vermek, biz teste yöntemleri var. Son olarak, değerlendirmek için bir yöntem var GameState. Dikkat RuleBooksadece bir veya diğer oyuncular zaten kazanmışsa açıklayan sorumlu olmalıdır, ancak bir oyunun orta daha iyi bir konumda olduğunu kim. Kimin daha iyi bir pozisyonda olduğuna karar vermek, kendi sınıfına taşınması gereken karmaşık bir şeydir. Bu nedenle, StateEvaluationsınıf aslında aşağıdaki gibi verilen basit bir numaradır:

package boardgame;

/**
 *
 */
public enum StateEvaluation {

    UNFINISHED,
    PLAYER_ONE_WINS,
    PLAYER_TWO_WINS,
    DRAW,
    ILLEGAL_STATE
}

Son olarak, GameHistorysınıfı tanımlayalım . Bu sınıf, oyunda ulaşılan tüm pozisyonları ve oynanan hamleleri hatırlamaktan sorumludur. Yapması gereken en önemli şey Move, oynanan bir kaydı kaydetmektir . Ayrıca geri almak için işlevler ekleyebilirsiniz Move. Aşağıda bir uygulamam var.

package boardgame;

import java.util.ArrayList;
import java.util.List;

/**
 *
 * @param <T> The type of GameState
 */
public class GameHistory<T> {

    private List<T> states;
    private List<Move<T>> moves;

    public GameHistory(T initialState) {
        states = new ArrayList<>();
        states.add(initialState);
        moves = new ArrayList<>();
    }

    void recordMove(Move<T> move) throws IllegalArgumentException {
        moves.add(move);
        states.add(move.makeResultingState(getMostRecentState()));
    }

    void resetToNthState(int n) {
        states = states.subList(0, n + 1);
        moves = moves.subList(0, n);
    }

    void undoLastMove() {
        resetToNthState(getNumberOfMoves() - 1);
    }

    T getMostRecentState() {
        return states.get(getNumberOfMoves());
    }

    T getStateAfterNthMove(int n) {
        return states.get(n + 1);
    }

    Move<T> getNthMove(int n) {
        return moves.get(n);
    }

    int getNumberOfMoves() {
        return moves.size();
    }

}

Sonunda, Gameher şeyi birbirine bağlamak için bir sınıf yapmayı hayal edebiliriz . Bu Gamesınıfın, insanlar için akımın ne olduğunu görmesini mümkün kılan, GameStatehangisinin varsa, hangi hamlelerin oynanabileceğini görebilecek ve hamle oynayabilecek yöntemleri görmesi gerekiyor. Aşağıda bir uygulama var

package boardgame;

import java.util.List;

/**
 *
 * @author brian
 * @param <T> The type of GameState
 */
public class Game<T> {

    GameHistory<T> gameHistory;
    RuleBook<T> ruleBook;

    public Game(RuleBook<T> ruleBook) {
        this.ruleBook = ruleBook;
        final T initialState = ruleBook.makeInitialState();
        gameHistory = new GameHistory<>(initialState);
    }

    T getCurrentState() {
        return gameHistory.getMostRecentState();
    }

    List<Move<T>> getLegalMoves() {
        return ruleBook.makeMoveList(getCurrentState());
    }

    void doMove(Move<T> move) throws IllegalArgumentException {
        if (!ruleBook.isMoveLegal(move, getCurrentState())) {
            throw new IllegalArgumentException("Move is not legal in this position");
        }
        gameHistory.recordMove(move);
    }

    void undoMove() {
        gameHistory.undoLastMove();
    }

    StateEvaluation evaluateState() {
        return ruleBook.evaluateState(getCurrentState());
    }

}

Bu sınıfta RuleBook, akımın ne olduğunu bilmekle sorumlu olmadığına dikkat edin GameState. Bu GameHistoryiş. Yani Gamesorar GameHistorygeçerli durumdur ve bu bilgiyi verir ihtiyaçları yasal hamle veya kimse kazandı eğer ne olduğunu söylemek.RuleBookGame

Her neyse, bu cevabın amacı, bir defada her bir sınıfın neyin sorumlu olduğunu belirledikten sonra, her bir sınıfı az sayıda sorumluluk üzerine odakladığınız ve her sorumluluğu kendine özgü bir sınıfa, ardından sınıflara verdiğinizdir. ayrıştırılma eğilimindedir ve her şeyin kodlanması kolaydır. Umarım verdiğim kod örneklerinde açıkça görülmektedir.


3

Deneyimlerime göre, dairesel referanslar genellikle tasarımınızın iyi düşünülmediğini gösteriyor.

Tasarımınızda, RuleBook'un Devlet hakkında neden "bilmesi" gerektiğini anlamıyorum. Bu bir gibi bir Devleti alabileceği parametre emin, bazı yönteme ama neden gerektiğinde biliyorum (yani bir örnek değişkeni olarak ele) bir devletin bir başvuru? Bu bana mantıklı gelmiyor. Bir Kural Kitabının işini yapabilmek için herhangi bir oyunun durumunu "bilmesi" gerekmez; Oyunun kuralları, oyunun mevcut durumuna bağlı olarak değişmez. Yani ya yanlış tasarladınız ya da doğru tasarladınız ama yanlış açıklıyorsunuz.


+1. Fiziksel bir masa oyunu satın alıyorsunuz, kuralları devletten açıklayabilen bir kural kitabı alıyorsunuz.
unperson325680

1

Dairesel bağımlılık mutlaka teknik bir sorun değildir, ancak genellikle Tek Sorumluluk İlkesi'nin ihlali olan bir kod kokusu olarak kabul edilmelidir .

Dairesel bağımlılığınız, Statenesnenizden çok fazla yapmaya çalıştığınız gerçeğinden geliyor .

Herhangi bir durum bilgisi olan nesne yalnızca bu yerel durumun yönetimi ile doğrudan ilgili yöntemler sağlamalıdır. En temel mantıktan daha fazlasını gerektiren bir şey varsa, muhtemelen daha büyük bir paternde parçalanmalıdır. Bazı insanlar bu konuda farklı görüşlere sahipler, ancak genel bir kural olarak, verilere ilişkin alıcılardan ve ayarlayıcılardan başka bir şey yapıyorsanız, çok fazla şey yapıyorsunuzdur.

Bu durumda, a hakkında daha iyi bir StateFactoryşey biliyor olabilirsiniz Rulebook. Muhtemelen StateFactoryyeni bir oyun oluşturmak için seni kullanan başka bir kontrolör sınıfına sahipsin . Statekesinlikle bilmemeliyim Rulebook. Kurallarınızın uygulanmasına bağlı olarak Rulebookbir şey biliyor olabilirsiniz State.


0

Bir kural kitabı nesnesinin belirli bir oyun durumuna bağlı olmasına gerek var mı, yoksa bir oyun durumu belirtildiğinde, bu durumdan hangi hareketlerin mümkün olduğunu bildirecek bir yöntemle bir kural kitabı nesnesine sahip olmak daha mantıklı olur mu (ve, Bunu bildirdikten sonra, söz konusu devlet hakkında hiçbir şey hatırlamayın) Mevcut hamleler hakkında sorulan nesnenin oyun durumunun bir hatırasını elinde bulundurmasıyla kazanılacak bir şey olmadığı sürece, bir referansı sürdürmesine gerek yoktur.

Bazı durumlarda, kuralları değerlendiren nesnenin durumunu korumasının avantajları olabilir. Böyle bir durumun ortaya çıkabileceğini düşünüyorsanız, "hakem" sınıfı eklemenizi ve kural defterinin "createReferee" yöntemi sunmasını öneririm. Bir oyun hakkında mı, yoksa elliden mi istendiğine hiç önem vermeyen kural kitabından farklı olarak, bir hakem nesnesi bir oyunu hakaret etmeyi bekler. Güçlendirdiği oyunla ilgili devletin tamamını kapsüle sokması beklenmez, ancak oyun hakkında yararlı olduğu düşünülen bilgileri önbelleğe alabilir. Bir oyun "geri al" işlevselliğini destekliyorsa, hakemin önceki oyun durumlarıyla birlikte saklanabilecek bir "anlık görüntü" nesnesi üretme aracı içermesi yararlı olabilir; bu nesne,

Bazı kavrama kurallara işleme ve aralarında gerekli olabilir Eğer bu mümkün tür bağlantı tutmak için yapacak bir hakem nesnesi kullanılarak, kod yönlerini oyun devlet işleme dışarı ana rulesbook ve oyun-devlet sınıflarının. Ayrıca, yeni kuralların, oyun durumu sınıfının konuyla ilgili olmadığını düşündüğü, oyun durumunun özelliklerini dikkate almasını mümkün kılabilir (örneğin, eğer “Z konumuna geldiyse“ Nesne X, Y yapamaz ”diyen bir kural eklenmişse). "hakem, hangi durumun Z'nin oyun sahası sınıfını değiştirmek zorunda kalmadan Z'ye geçtiğini takip etmek için değiştirilebilir).


-2

Bununla baş etmenin doğru yolu arayüzleri kullanmaktır. İki sınıfı birbirinden tanımak yerine, her bir sınıfın diğer sınıftaki arayüzünü ve referansını kullanmasını sağlayın. Diyelim ki birbirinize referans vermeniz gereken A sınıfı ve B sınıfı var. A sınıfı A arabirimine ve B sınıfı B arabirimine sahipseniz, B sınıfı A arabirimine ve B sınıfı A arabirimine başvurabilirsiniz. A Sınıfı, B projesinde olduğu gibi kendi projesinde olabilir. diğer iki projenin de referansı.


2
bu sadece, sadece birkaç saat önce yayınlanan bir cevapta yapılan ve açıklanan puanları tekrar ediyor gibi görünüyor
gnat
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.