Как избежать дублирования кода интерфейса?

Поскольку интерфейсы не могут содержать реализацию, мне кажется, что это приводит к дублированию кода в классах, которые наследуются от интерфейса. В приведенном ниже примере сделайте вид, что, скажем, первые 10 или около того строк, которые устанавливают чтение из потока, дублируются. Попытайтесь не сосредоточиться на формулировке здесь, но вместо этого сосредоточьтесь на концепции того, насколько легко создавать дублирующий код между каждым классом.

Например:

public interface IDatabaseProcessor
{
   void ProcessData(Stream stream);
}
public class SqlServerProcessor : IDatabaseProcessor
{
    void ProcessData(Stream stream)
    {
      // setting up logic to read the stream is duplicated code
    }
}
public class DB2Processor : IDatabaseProcessor
{
    void ProcessData(Stream stream)
    {
      // setting up logic to read the stream is duplicated code
    }
}

Я понимаю, что использование абстрактного базового класса для ProcessData и добавление не абстрактных членов - одно из решений. Однако, что, если я действительно, действительно хочу использовать интерфейс вместо этого?

Ответ 1

Лучший способ разделить код между интерфейсами - это методы расширения без состояния. Вы можете создавать эти расширения один раз и использовать его во всех классах, реализующих интерфейс, независимо от их цепочки наследования. Это то, что .NET сделал с IEnumerable<T> в LINQ, для весьма впечатляющих результатов. Это решение не всегда возможно, но вы должны это делать, когда можете.

Другим способом обмена логикой является создание внутреннего класса "помощник". Это похоже на правильный выбор в вашем случае: реализации могут вызывать внутренний код в качестве вспомогательных методов, без необходимости дублировать код. Например:

internal static class SqlProcessorHelper {
    public void StreamSetup(Stream toSetUp) {
        // Shared code to prepare the stream
    }
}
public class SqlServerProcessor : IDatabaseProcessor {
    void ProcessData(Stream stream) {
        SqlProcessorHelper.StreamSetup(stream);
    }
}
public class DB2Processor : IDatabaseProcessor {
    void ProcessData(Stream stream) {
        SqlProcessorHelper.StreamSetup(stream);
    }
}

Класс-помощник не обязательно должен быть статичным: если ваши общие методы нуждаются в состоянии, вы можете сделать своего помощника обычным классом и поместить его экземпляр в каждую реализацию вашего интерфейса, где вы хотели бы поделиться кодом.

Ответ 2

Это случай, когда вы хотите использовать как интерфейс, так и абстрактный базовый класс.

Единственная причина, по которой вы столкнулись, состоит в том, что другой класс не будет делиться абстрактным базовым кодом, но будет соблюдать интерфейс. Рассмотрим:

public interface IDatabaseProcessor {
   void ProcessData(Stream stream);
}

public abstract class AbstractDatabaseProcessor : IDatabaseProcessor {
    public void ProcessData(Stream stream) {
      // setting up logic to read the stream is not duplicated
    }
}

public class SqlServerProcessor : AbstractDatabaseProcessor {
    //SqlServerProcessor specific methods go here
}

public class DB2Processor : AbstractDatabaseProcessor {
    // DB2Processor specific methods go here
}

public class NonSharedDbProcessor : IDatabaseProcessor {
    void ProcessData(Stream stream) {
      // set up logic that is different than that of AbstractDatabaseProcessor
    }
}

Синтаксис может быть немного неактивным, я не обычный пользователь С#. Я пришел сюда через тег OOP.

Ответ 3

Как вы уже сказали, один из вариантов использует базовый абстрактный (или может быть даже не абстрактный) класс. Другой вариант - создать другой объект для запуска общего кода. В вашем случае это может быть DataProcessor:

internal class DataProcessor
{
    public void Do(Stream stream) 
    {
        // common processing here
    }
}
public class SqlServerProcessor : IDatabaseProcessor
{
    void ProcessData(Stream stream)
    {
        new DataProcessor().Do(stream);
    }
}
public class DB2Processor : IDatabaseProcessor
{
    void ProcessData(Stream stream)
    {
        new DataProcessor().Do(stream);
    }
}

Ответ 4

Как вы указываете, класс abstract предоставляет решение. Вы можете использовать интерфейс, если вы "действительно, действительно хотите"; ничто не исключает его. Ваш класс abstract должен реализовать IDatabaseProcessor.

Ответ 5

Если вы действительно не хотите использовать базовый класс, все еще имея доступ к членам private и/или protected из общего кода, тогда доступен только другой доступный генерация кода. Он встроенный VS (был очень долгое время) и очень мощный.

Ответ 6

Просто используйте интерфейсы с абстрактным базовым классом:

public interface IDatabaseProcessor
{
   void ProcessData(Stream stream);
}
public abstract class AbstractDatabaseProcessor : IDatabaseProcessor
{
    public virtual void ProcessData(Stream stream)
    {
      // setting up logic to read the stream is duplicated code
    }
}
public class SqlServerProcessor : AbstractDatabaseProcessor
{
    public void ProcessData(Stream stream)
    {
        base.ProcessData(stream);

        // Sql specific processing code
    }
}
public class DB2Processor : AbstractDatabaseProcessor
{
    public void ProcessData(Stream stream)
    {
        base.ProcessData(stream);

        // DB2 specific processing code
    }
}

Ответ 7

Хорошо иметь некоторые иерархии классов для реализации интерфейса для совместного использования некоторой реализации интерфейса.

т.е. в вашем случае вы можете переместить общий код ProcessData на что-то вроде ProcessorBase и вывести из него как DB2Processor, так и SqlServerProcessor. Вы можете решить, какой уровень реализует интерфейс (т.е. Вы можете по какой-то причине иметь только SqlServerProcessor реализовать интерфейс IDatabaseProcessor - он все равно будет забирать ProcessData из базового класса в качестве реализации интерфейса).