Bir nesneyi sunum yapan kişiyle eşlemenin OOP yolunu temizleme


13

Her parça (gibi kendi türüdür Java, (örneğin satranç gibi) bir masa oyunu yaratacağım Pawn, Rookvs.). Uygulamanın GUI kısmı için bu parçaların her biri için bir görüntüye ihtiyacım var. Yaptığımdan beri

rook.image();

UI ve iş mantığının ayrılmasını ihlal ediyor, her parça için farklı bir sunucu oluşturacağım ve daha sonra parça türlerini

private HashMap<Class<Piece>, PiecePresenter> presenters = ...

public Image getImage(Piece piece) {
  return presenters.get(piece.getClass()).image();
}

Çok uzak çok iyi. Ancak, ihtiyatlı bir OOP gurusunun bir getClass()yöntem çağrıldığında kaşlarını çattığını ve örneğin böyle bir ziyaretçi kullanmanızı önerdiğini hissediyorum :

class Rook extends Piece {
  @Override 
  public <T> T accept(PieceVisitor<T> visitor) {
    return visitor.visitRook(this);
  }
}

class ImageVisitor implements PieceVisitor<Image> {
  @Override  
  public Image visitRook(Rook rook) {
    return rookImage;
  } 
}

Bu çözümü seviyorum (teşekkür ederim, guru), ancak önemli bir dezavantajı var. Uygulamaya her yeni parça türü eklendiğinde, PieceVisitor yeni bir yöntemle güncellenmelidir. Sistemimi, çerçeve kullanıcısının sadece hem parçanın hem de sunum yapan kişinin uygulanmasını sağlayacağı ve çerçeveye ekleyeceği basit bir işlemle yeni parçaların eklenebileceği bir masa oyunu çerçevesi olarak kullanmak istiyorum. Benim sorum: Bu tür genişletilebilirliğe izin verecek instanceof, getClass()vb. Olmadan temiz bir OOP çözümü var mı?


Her satranç taşı türü için kendi sınıfına sahip olmanın amacı nedir? Bir parça nesnesinin tek bir parçanın konumunu, rengini ve türünü tuttuğunu düşünüyorum. Muhtemelen bir Piece sınıfı ve bu amaçla iki numara (PieceColor, PieceType) olurdu.
GELMEKTEDİR

@COMEFROM açıkçası, farklı türdeki parçaların farklı davranışları vardır, bu nedenle sözde kayalar ve piyonlar arasında ayrım yapan bazı özel kodların olması gerekir. Bununla birlikte, genel olarak tüm türleri işleyen ve davranışı özelleştirmek için Strateji nesnelerini kullanan standart bir parça sınıfına sahip olmayı tercih ederim.
Jules

@Jules ve davranışı içeren her bir parça için ayrı bir sınıfa sahip olmak üzerine her bir parça için bir stratejiye sahip olmanın yararı ne olurdu?
lishaak

2
Oyunun kurallarını, tek tek parçaları temsil eden durumsal nesnelerden ayırmanın açık bir yararı, derhal sorununuzu çözmesidir. Sıra tabanlı bir masa oyunu uygularken kural modelini devlet modeli ile karıştırmazdım.
GELMEKTEDİR

2
Kuralları ayırmak istemiyorsanız, hareket kurallarını altı PieceType nesnesinde (sınıflar değil!) Tanımlayabilirsiniz. Her neyse, bence karşılaştığınız sorunlardan kaçınmanın en iyi yolu endişeleri ayırmak ve kalıtımı sadece gerçekten yararlı olduğunda kullanmaktır.
GELMEKTEDİR

Yanıtlar:


10

Bu tür genişletilebilirlik sağlayan instanceof, getClass () vb. olmayan temiz bir OOP çözümü var mı?

Evet var.

Size bunu sorayım. Mevcut örneklerinizde parça türlerini görüntülerle eşleştirmenin yollarını buluyorsunuz. Bu, taşınan bir parçanın sorununu nasıl çözer?

Tip sormaktan daha güçlü bir teknik Söylemektir, sorma . Her bir parça bir PiecePresenterarayüz aldıysa ve şöyle görünüyorsa:

class PiecePresenter implements PieceOutput {

  BoardPresenter board;
  Image pieceImage;

  @Override
  PiecePresenter(BoardPresenter board, Image image) {
    public void display(int rank, int file) {
      board.display(pieceImage, rank, file);
    } 
  }
}

İnşaat şöyle görünecektir:

rookWhiteImage = new Image("Rook-White.png");
PieceOutput rookWhiteOutPort = new PiecePresenter(boardPresenter, rookWhiteImage);
PieceInput rookWhiteInPort = new Rook(rookWhiteOutPort);
board[0, 0] = rookWhiteInPort;

Kullanımı şöyle görünecektir:

board[rank, file].display(rank, file);

Buradaki fikir, başka şeylerin sorumlu olduğu herhangi bir şey için sorumluluk sormaktan kaçınmak ya da buna dayanarak karar vermekten kaçınmaktır. Bunun yerine, bir şey hakkında ne yapacağını bilen bir şeye referans verin ve bildikleriniz hakkında bir şeyler yapmasını söyleyin.

Bu polimorfizme izin verir. Konuştuğun şeyi önemsemiyorsun. Ne söylemesi önemli değil. İhtiyacınız olanı yapabileceğini umursuyorsunuz.

Ayrı katmanlar halinde bu tutar İyi bir diyagramı, söyle-yok-ask izler ve nasıl haksız yere katman değil çift katman etmektir gösterileri bu :

resim açıklamasını buraya girin

Burada kullanmadığımız (ve kesinlikle ekleyebileceğimiz) bir kullanım senaryosu katmanı ekliyor, ancak sağ alt köşede gördüğünüz aynı deseni izliyoruz.

Presenter'ın miras kullanmadığını da fark edeceksiniz. Kompozisyon kullanır. Kalıtım, polimorfizm elde etmenin son çare yolu olmalıdır. Kompozisyon ve delegasyon kullanmayı tercih eden tasarımları tercih ederim. Biraz daha klavye yazmak ama çok daha fazla güç.


2
Bu muhtemelen iyi bir cevap (+1) olduğunu düşünüyorum, ancak çözümünüzün Temiz Mimari için iyi bir örnek olduğuna ikna olmadım: Rook varlığı şimdi çok doğrudan bir sunucuya referans veriyor. Temiz Mimari'nin engellemeye çalıştığı bu tür bir bağlantı değil mi? Ve cevabınızın tam olarak ne anlama gelmediği: varlıklar ve sunum yapan kişiler arasındaki eşleme şimdi varlığı başlatan herkes tarafından ele alınmaktadır. Bu muhtemelen alternatif çözümlerden daha zarif, ancak yeni varlıklar eklendiğinde değiştirilecek üçüncü bir yer - şimdi Ziyaretçi değil, Fabrika.
amon

@ amon, dediğim gibi, kullanım örneği katmanı eksik. Terry Pratchett bu tür şeyleri "çocuklara yalan söylüyor" olarak adlandırdı. Ezici bir örnek yaratmamaya çalışıyorum. Temiz Mimarlık konusunda okula götürülmem gerektiğini düşünüyorsanız, sizi burada göreve götürmeye davet ediyorum .
candied_orange

üzgünüm, hayır, size okul yapmak istemiyorum, sadece bu mimari modeli daha iyi anlamak istiyorum. Tam anlamıyla bir aydan daha kısa bir süre önce “temiz mimari” ve “altıgen mimari” kavramlarına rastladım.
amon

@ amon bu durumda iyi bir görünüm verin ve sonra beni göreve götürün. Yapsan çok isterdim. Hala bunun bazı kısımlarını kendim anlıyorum. Şu anda bir menü odaklı python projesini bu stile yükseltmek için çalışıyorum. Temiz Mimari hakkında eleştirel bir inceleme burada bulunabilir .
candied_orange

1
Başta @lishaak örneklemesi olabilir. İç katmanlar dış katmanları bilmiyor. Dış katmanlar sadece arayüzleri bilir.
candied_orange

5

Buna ne dersin:

Modelinizin (şekil sınıfları) başka bağlamlarda da ihtiyaç duyabileceğiniz ortak bir yöntemi vardır:

interface ChessFigure {
  String getPlayerColor();
  String getFigureName();
}

Belirli bir şekli görüntülemek için kullanılacak görüntüler bir adlandırma şemasına göre dosya adları alır:

King-White.png
Queen-Black.png

Daha sonra java sınıfları hakkındaki bilgilere erişmeden uygun resmi yükleyebilirsiniz.

new File(FIGURE_IMAGES_DIR,
         String.format("%s-%s.png",
                       figure.getFigureName(),
                       figure.getPlayerColor)));

Ayrıca, potansiyel olarak büyüyen bir sınıf setine bazı bilgiler (sadece görüntüler değil) eklemem gerektiğinde bu tür sorunların genel çözümüyle ilgileniyorum. "

Bence bu kadar derse odaklanmamalısın . Daha ziyade iş nesneleri açısından düşünün .

Ve jenerik çözüm olduğunu haritalama her türlü. IMHO hile bu eşleme koddan bakımı daha kolay bir kaynağa taşımaktır.

Örneğim, bu eşlemeyi uygulanması oldukça kolay olan ve iş modeline görünümle ilgili bilgileri eklemekten kaçınan sözleşmeyle yapıyor . Öte yandan, hiçbir yerde ifade edilmediğinden "gizli" bir eşleme olarak düşünebilirsiniz.

Başka bir seçenek, bunu , eşlemeyi içeren bir kalıcılık katmanı dahil olmak üzere kendi MVC katmanlarıyla ayrı bir iş durumu olarak görmektir .


Bunu, bu özel senaryo için çok pratik ve yeryüzüne bir çözüm olarak görüyorum. Potansiyel olarak büyüyen bir sınıf setine bazı bilgiler (sadece görüntüler değil) eklemem gerektiğinde de bu tür sorunlara genel çözümle ilgileniyorum.
lishaak

3
@lishaak: Buradaki genel yaklaşım , iş nesnelerinize yeterli miktarda meta veri sağlamaktır , böylece bir kaynak veya kullanıcı arabirimi öğelerine genel eşleme otomatik olarak yapılabilir.
Doc Brown

2

Görsel bilgileri içeren her parça için ayrı bir UI / view sınıfı yaratacağım. Bu parça görüntüleme sınıflarının her birinde, parçanın konumunu ve oyun kurallarını içeren model / iş muadili için bir işaretçi vardır.

Örneğin bir piyon alın:

class Pawn : public Piece {
public:
    Vec2 position() const;
    /**
     The rest of the piece's interface
     */
}

class PawnView : public PieceView {
public:
    PawnView(Piece* piece) { _piece = piece; }
    void drawSelf(BoardView* board) const{
         board.drawPiece(_image, _piece->position);
    }
private:
    Piece* _piece;
    Image _image;
}

Bu, mantık ve kullanıcı arayüzünün tamamen ayrılmasını sağlar. Mantık parçası işaretçisini parçaların hareketini idare edecek bir oyun sınıfına aktarabilirsiniz. Tek dezavantajı, örneklemenin bir UI sınıfında olması gerektiğidir.


Tamam, diyelim ki biraz var Piece* p. PawnViewBir RookViewveya değil, görüntülemek için bir oluşturmak zorunda olduğumu nasıl bilebilirim KingView? Yoksa yeni bir Parça oluşturduğumda hemen eşlik eden bir görünüm veya sunum oluşturmam mı gerekiyor? Bu temelde @ CandiedOrange'in tersine çevrilmiş bağımlılıklar ile çözümü olacaktır. Bu durumda, PawnViewyapıcı Pawn*sadece a değil, a alabilir Piece*.
amon

Evet, üzgünüm, PawnView yapıcısı bir Piyon * alacaktı. Ve aynı zamanda bir PawnView ve bir Piyon oluşturmak zorunda değilsiniz. 100 Piyon'a sahip olabilen bir oyun olduğunu varsayalım, ancak bir seferde sadece 10 görsel olabilir, bu durumda birden fazla Piyon için piyon görüntülerini tekrar kullanabilirsiniz.
Lasse Jacobs

Ve ben sadece 2 sent paylaşmak istiyorum @ CandiedOrange çözüm ile katılıyorum.
Lasse Jacobs

0

Ben Pieceparametresi parça türünü tanımlayan bir numaralandırma türü olduğu jenerik yaparak bu yaklaşımı , her parça böyle bir tür referans var. Daha sonra kullanıcı arayüzü numaralandırmadan önceki gibi bir harita kullanabilir:

public abstract class Piece<T>
{
    T type;
    public Piece (T type) { this.type = type; }
    public T getType() { return type; }
}
enum ChessPieceType { PAWN, ... }
public class Pawn extends Piece<ChessPieceType>
{
    public Pawn () { super (ChessPieceType.PAWN); }

Bunun iki ilginç avantajı vardır:

Birincisi, statik olarak yazılan çoğu dil için geçerlidir: Tahtanızı xpect için bir parça türü ile parametrelendirirseniz, içine yanlış türde bir parça ekleyemezsiniz.

İkincisi ve belki de daha ilginç bir şekilde, Java'da (veya diğer JVM dillerinde) çalışıyorsanız, her bir enum değerinin sadece bağımsız bir nesne olmadığını, aynı zamanda kendi sınıfına da sahip olabileceğini unutmayın. Bu, parçanın davranışını müşteriye göre parça tipi nesnelerinizi Strateji nesneleri olarak kullanabileceğiniz anlamına gelir:

 public class ChessPiece extends Piece<ChessPieceType> {
    ....
   boolean isMoveValid (Move move)
    {
         return getType().movePatterns().contains (move.asVector()) && ....


 public enum ChessPieceType {
    public abstract Set<Vector2D> movePatterns();
    PAWN {
         public Set<Vector2D> movePatterns () {
              return Util.makeSet(
                    new Vector2D(0, 1),
                    ....

(Açıkçası gerçek uygulamaların bundan daha karmaşık olması gerekir, ancak umarım fikri anlarsınız)


0

Pragmatik programcıyım ve temiz veya kirli mimarinin ne olduğu gerçekten umrumda değil. Gereksinimlere inanıyorum ve basit bir şekilde ele alınması gerekiyor.

İhtiyacınız, satranç uygulama mantığınızın web, mobil uygulama veya hatta konsol uygulaması gibi farklı sunum katmanlarında (cihazlarda) temsil edilmesidir, bu nedenle bu gereksinimleri desteklemeniz gerekir. Her cihazda çok farklı renkler, parça görüntüler kullanmayı tercih edebilirsiniz.

public class Program
{
    public static void Main(string[] args)
    {
        new Rook(new Presenter { Image = "rook.png", Color = "blue" });
    }
}

public abstract class Piece
{
    public Presenter Presenter { get; private set; }
    public Piece(Presenter presenter)
    {
        this.Presenter = presenter;
    }
}

public class Pawn : Piece
{
    public Pawn(Presenter presenter) : base(presenter) { }
}

public class Rook : Piece
{
    public Rook(Presenter presenter) : base(presenter) { }
}

public class Presenter
{
    public string Image { get; set; }
    public string Color { get; set; }
}

Gördüğünüz gibi, sunucu parametresinin her cihaza (sunum katmanı) farklı şekilde aktarılması gerekir. Bu, sunum katmanınızın her bir parçayı nasıl temsil edeceğinize karar vereceği anlamına gelir. Bu çözümde yanlış olan ne?


Öncelikle, parça sunum katmanının orada olduğunu ve görüntü gerektirdiğini bilmelidir. Bazı sunum katmanları resim gerektirmiyorsa ne olur? İkincisi, bir parçanın sunumcu olmadan var olamayacağı için, parçanın UI katmanı tarafından başlatılması gerekir. Oyunun, kullanıcı arayüzüne ihtiyaç duyulmayan bir sunucuda çalıştığını düşünün. O zaman bir parçayı başlatamazsınız çünkü kullanıcı arayüzünüz yok.
lishaak

Temsilciyi isteğe bağlı parametre olarak da tanımlayabilirsiniz.
Freshblood

0

Kullanıcı arayüzünü ve alan mantığını tamamen soyutlamanıza yardımcı olacak başka bir çözüm daha var. Anakartınızın UI katmanınıza maruz kalması gerekir ve UI katmanınız parçaları ve konumları nasıl temsil edeceğinize karar verebilir.

Bunu başarmak için Fen dizesini kullanabilirsiniz . Fen string temel olarak kart durum bilgisidir ve mevcut parçaları ve gemideki konumlarını verir. Bu nedenle, panonuzda, Fen dize yoluyla panonun mevcut durumunu döndüren bir yöntem olabilir, ardından UI katmanınız panosu istediği gibi temsil edebilir. Aslında şu anki satranç motorları böyle çalışıyor. Satranç motorları GUI'siz konsol uygulamasıdır, ancak harici GUI ile kullanırız. Satranç motoru grafik dizileri ve satranç notasyonu ile GUI ile iletişim kurar.

Yeni bir parça eklersem ne soruyorsun? Satrancın yeni bir parça getireceği gerçekçi değildir. Bu, alan adınızda büyük bir değişiklik olurdu. YAGNI prensibini takip edin.


Bu çözüm kesinlikle işlevsel ve fikri takdir ediyorum. Ancak, satrançla çok sınırlıdır. Genel problemi göstermek için satrancı daha çok örnek olarak kullandım (bu soruda daha açık olabilirdim). Önerdiğiniz çözüm başka bir alanda kullanılamaz ve doğru bir şekilde söylediğiniz gibi, yeni iş nesneleri (parçaları) ile genişletmenin bir yolu yoktur. Gerçekten satranç daha fazla
taş
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.