OOP şekilde yumurta-tavuk problemi

13 Cevap php

Ben bir tavuk-yumurta problemi var. Veritabanı ve Log: ben, bir OOP şekilde PHP bir sistem uygulamak çok hangi iki sınıfları önemli rol oynayacağını istiyorum. Benim fikrim kamu yöntemleri örneğin olurdu Veritabanı sınıfı tarafından bağlantı kurmak oldu. runQuery (sUpdateQuery), doInsert (sInsert), vb Log sınıfı sadece VERİTABANINA LogMessage (mesaj), logDatabaseQuery (sQuery) gibi yaygın yöntemlerle günlüklerini yazmak istiyorum. Sorun şimdi geliyor.

1: Veri tabanlarının sınıfının yöntemleri içinde ben Log sınıfının logDatabaseQuery (sQuery) kullanmak mümkün istiyorum

2: Ben logDatabaseQuery yöntemi içinde Veritabanı sınıfının doInsert (sInsert) yöntemini kullanmak istemez ki eğer bu hala büyük bir sorun olmaz.

Ben basit tutmak istiyorum - ve eğer mümkünse sadece, iyi veritabanı bağlantı nesnesinin bir örneğini kullanın ve loggeras gelen.

Birçok kişi için Singleton modeli seçmek için ilk fikir olabilir, ama kesinlikle farklı bir çözüm kullanmak istiyorum.

Yani her-Diğer yöntemlerini kullanmak istiyorsunuz iki sınıf olacaktır:

Database doInsert logDatabaseQuery

Log logDatabaseQuery doInsert

Daha sonra bunun üzerinde de sadece veritabanı, ancak dosya veya e-postalara giriş için başka yöntemler olurdu çünkü, (Giriş sınıfı) ayrı ayrı Log yöntemleri tutmak istiyorum.

Herhangi bir fikir, / OOP dostu bir şekilde, en güzel, bir de bu nasıl yapılabilir ki?

Ben ortak bir ebeveyn soyut sınıf düşünmeye, ya da yanı sıra arayüzleri kullanarak hakkında, ama sonunda doğru yolu çözemedim oldu: (

Ne bilmek istiyorum doğru bir sınıf hiyerarşisi için bir öneri

13 Cevap

Birlikte çok şey bir araya getirdik.

Veritabanı gerçekten veritabanına bağlıdır, bir logger bağlı olamaz. Bu iyi bir tasarım değil.

Ne gerçekten sahip veritabanı erişimi iki türü vardır.

Düşük seviye erişim "ham" SQL yapar.

Logger bu alt düzey sınıfına bağlıdır. O değil - kendisi - o ham SQL var. Bir alt düzey sınıfına bağlıdır.

Yüksek seviyeli erişim, düşük düzeyli erişim and logger uygulama sorguları kullanır gelmez.

Kaydedilir (normal durumda) ve (günlük fonksiyonlar için) unlogged - iki farklı veritabanı erişen var gibi geliyor. Ikiye veritabanı sınıfı, giriş istekleri için daha üst düzey bir versiyonu, ve unlogged istekleri için bir alt düzey sürümünü bölünmüş. Günlüğü referanslar ve alt düzey veritabanı sınıfları kullanarak üst düzey veritabanı sınıfı uygulamak.

Bir günlükçüsüne veritabanı nesne erişim vermek istiyorsanız, o zaman için logger geçmek. Eğer bir veritabanı nesnesine logger'inizi erişim vermek istiyorsanız, o zaman için veritabanı nesnesini iletin.

Eğer onlar tarafından sağlanan işlevleri kullanmadan önce bu nesnelerin sınıf içinde var olup olmadığını kontrol edin.

PHP varsayılan referans ile bu nesneleri geçer beri, çeşitli nesneler için tek bir logger ve veritabanı sınıf ile yapabilirsiniz.

Ben de size logger tek bir noktada veritabanı yazılı her şeyi tavsiye ederim. Sadece geçici bir dizideki her şeyi saklamak ve yapısızlaştırmak zaman DB kodu çalıştırın. Eğer kod boyunca veritabanına yazıyoruz Aksi halde, yükü bir ton yaratmak olacak.

class Database {
    private $_logger = 0; //contains your logger
    /* The rest of your class goes here */

    //Add the logger to your class so it may access it.
    public function setLogger($logger) {
        $this->_logger = $logger;
    }

    //To check if we have a logger, we don't want to use one if we lack one.
    public function hasLogger() { 
        if($this->_logger) return true; 
        else return false;
    }
}
class Logger {
    private $_database = 0; //variable to hold your database object
    /* The rest of your class goes here */

    public function setDatabase($database) { //Same crap basically
        $this->_database = $database;
    }

    public function hasDatabase() {
        if($this->_database) return true;
        else return false;
    }

    public function doSomething { //This is new though
        if($this->hasDatabase()) { //Shows how you use hasDatabase()
            $this->_database->someDatabaseFunction();
        }
        else {
            //Whatever you'd do without a database connection
        }
    }
}
$database = new Database;
$logger = new Logger;

$database->setLogger($logger);
$logger->setDatabase($database);

Decouple Database from Logger. I would design the application to log from the middle tier and make the decision about which logger type to use at runtime not compile time. I.e. use a factory method to determine whether to log to db/xml/whatever.

Veri Katmanı log (yani sorunu rapor) bir istisna var, orta katman onu yakalamak ve daha sonra orada işlemek veya bir teşhis sınıfa onu el ve karar olması nasıl karar vermeniz gerekir yoksa. Ben ne olduğunu ve ne bir loggable olay değildir hakkında kararlar yok mümkün olduğu kadar "kör / dilsiz" olarak DAL tutmak ve her iki şekilde olur.

Ortak bir üst sınıf bir iyi bir fikir değildir. Bir logger bir veritabanı [r] değildir. Ve ne de bir logger bir veritabanı. Onun çok değil, bir inek ve bir domuz sorun olarak bir tavuk ve yumurta sorunu. Bunlar iki farklı hayvanlardır. Veritabanı logger haberdar olmak için hiçbir neden yoktur. Ben soyutlama zorluyor düşünüyorum. I görüyorum tüm bir db logger ise bir Logger .... bir Veritabanı olmasıdır.

Zaten orta katman gelen veri katmanı sürücü, yani istisnalar ve olaylar da dahil olmak üzere ne zaman günlük db ilgili olaylar vefa kaybedecek nerede, ben göremiyorum.

 public interface ILoggingProvider
    {
       void Log(String message)
    }

    public static class Logger
    {
       public static ILoggingProvider GetLoggingProvider()
       {
          //factory to return your logger type
       }

       public void Log(String message)
       {
          Logger.GetLoggingProvider().Log(message);
       }
    }

    public class DBLogger : ILoggingProvider {

      void Log(string message) {
       Database db = new Database();
       db.doInsert(someLoggingProc);
     }
    }

    public class Database
    {
        runQuery(sUpdateQuery) 
        doInsert(sInsert)
    }

...


 public class Customer{

 public void SaveCustomer(Customer c)
 {
   try {
    // Build your query
    Database db = new Database();
    db.runQuery(theQuery);
   } catch (SqlException ex) {
    Logger.Log(ex.message);
   }
 }
}

Bir arayüz ILogger uygulayan bir Logger sınıf oluşturmak. Logger sınıfının yeni bir örneği çıktı günlük iletileri için kullanılan arayüz ILoggingProvider uygulayan bir nesne alır.

ILoggingProvider arabirimini uygulayan bir veritabanı sınıf oluşturmak. Veritabanı yeni bir örneği mesajları oturum için kullanılan ILogger arabirimini uygulayan bir nesne alır.

public interface ILogger
{
   void Debug(String message)
}

public interface ILoggingProvider
{
   void Log(String message)
}

public class Logger : ILogger
{
   private ILoggingProvider LoggingProvider { get; set; }

   public Logger(ILoggingProvider loggingProvider)
   {
      this.LoggingProvider = loggingProvider;
   }

   public void Debug(String message)
   {
      this.LoggingProvider.Log(message);
   }
}

public class Database : ILoggingProvider
{
   private ILogger Logger { get; set; }

   public Database(ILogger logger)
   {
      this.Logger = logger;
   }

   public void DoStuffWithTheDatabase()
   {
      // Do stuff with the database
      this.Logger.Debug("Did stuff with the database.");
   }

   public void Log(String message)
   {
      // Store message to database - be carefull not to generate new
      // log messages here; you can only use the subset of the Database
      // methods that do not themselve generate log messages
   }
}

Günlük olmadan uçlar için izin veren bir ekleme yöntemi aşırı oluşturun ve günlük sınıf kullanan var. Aksi takdirde, tasarım tanımı gereği özyinelemelidir: DB ekleme yaparak tüm DB ekler yapın.

için isteğe bağlı bir parametre eklemek doInsert $ callerIsLogger = false, sonra her zaman ikinci parametre true sağlamak, sizin logger dan doInsert () gerekir, ve doInsert bu durumun da kontrol edebilir logger denir logger demiyorlar. Ne hiyerarşileri? KISS metodoloji kayalar :)

Bu Mediator Pattern için iyi bir aday. Sen logger ve veritabanı hem de çeşitli yöntemleri çağırmak istiyorum, ve bu arabulucu nesne logger ve veritabanına başvurular tutmak ve sizin için iletişimi ele olacağı bir nesne olurdu.

Sen Veritabanı ve Logger hem de sonsuz bir günlük döngüye girmesini önlemek için kullanabilirsiniz (ve aynı zamanda dairesel bağımlılığı önlemek) o başka bir sınıf ekleyebilirsiniz.

// Used only by Logger and DatabaseProxy
class Database {
   function doInsert( $sql ) {
      // Do the actual insert.
   }
}

class Logger {
   function log($sql) {
     $msg_sql = ...
     Database.doInsert($msg_sql);
}

// Used by all classes
class DatabaseProxy {
   function doInsert( $sql ) {
      Logger.log($sql);
      Database.doInsert($sql);
   }
}

Sen Veritabanı ve DatabaseProxy hem de ortak bir arabirim sağlayarak biraz giyinmek ve uygun örneği sağlamak için bir fabrika kullanabilirsiniz.

IMHO, veritabanı sınıfı logger ile etkileşim olmamalıdır. Ben veritabanı sınıfını çağırıyor kod aynı kısmından logger aramak için eğimli olacak. Sonra logger kendi ekler yapmak için veritabanı sınıfını kullanabilirsiniz.

Eğer odaklı nesne yapma uğruna sadece basit bir soruna komplikasyon bir sürü ekleme gibi geliyor ...

kendine şunu sor, gerçekten bu yaklaşım alarak ne kazanıyor?

Sen Database::insert() fonksiyonu için ikinci bir arg yapabilir:

function insert($sql, $logThis = true) { ... }

Logger onu çağırır Ve sonra tabii ki, ikinci argüman yanlış yapmak.

Alternatif olarak, sadece insert işlevi Günlüğü sınıfından çağrıldı görmek için fonksiyon yığını kontrol.

function insert($sql) {
    $callStack = debug_backtrace();
    if (!isset($callStack[1]) || $callStack[1]['class'] !== "Logger") {
        Logger::logSQL($sql);
    }
    // ...
}

Ben bunu nasıl:

public interface ILogger
{
    void LogWarning(string message);
    void LogMessage(string message);
}

public interface ILoggingProvider
{
    void LogEntry(string type, string message);
}

public interface IDbProvider
{
    void DoInsert(string insertQuery, params object[] parameters);
}

public class Logger: ILogger
{
    private ILoggingProvider _provider = ProviderFactory.GetLogProvider();

    public void LogWarning(string message)
    {
        _provider.LogEntry("warn", message);
    }

    public void LogMessage(string message)
    {
        _provider.LogEntry("message", message);
    }

    public void LogEntry(string type, string message)
    {
        _provider.LogEntry(type, message);
    }
}

public class Database : IDbProvider
{
    private Logger _logger = new Logger();
    public void DoInsert(string insertQuery, params object [] parameters)
    {
        _logger.LogEntry("query", insertQuery);
    }
}

public class DbLogProvider : ILoggingProvider
{
    private IDbProvider _dbProvider = ProviderFactory.GetDbProvider();

    public void LogEntry(string type, string message)
    {
        _dbProvider.DoInsert("insert into log(type,message) select @type,@message",type,message);
    }

}

public static class ProviderFactory
{
    public static IDbProvider GetDbProvider()
    {
        return new Database();
    }


    internal static ILoggingProvider GetLogProvider()
    {
        return new DbLogProvider();
    }
}

Ben de muhtemelen oldukça ProviderFactory sınıfta onları hardcoding daha bir yapılandırma dosyasından bir tür bakmak yapacak rağmen. Burada varsayım kod tüm günlük yürütme sırasında bir şekilde yapılabilir olacağını yönetici kalmış, bu günlüğe nasıl bakım, ve bu olmamasıdır (ki olur benim tercihi neyse). Açıkçası günlükçü oluştururken günlük hedef olarak bir seçim yapmak için bu genişletmek ve uygun sağlayıcı oluşturabilirsiniz.