Как я могу избежать необходимости постоянно менять интерфейс при добавлении новых функций в систему C # - PullRequest
0 голосов
/ 06 сентября 2018

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

Пример:

Возьмем, к примеру, нашу эволюционную систему. Я создал интерфейс IEvolvable, у которого есть свойство для уровня развития и метод Evolve ().

public interface IEvolvable
{
    int evolution { get; }

    bool IncreaseEvolution(int numEvolutions);
}

Затем у меня есть реализация этого интерфейса в классе Character, и на основе некоторых условий через мой класс обработки Evolution я хочу развить своего персонажа.

public class EvolutionHandler
{
    public IEvolvable evolvable;

    public void TryEvolveCharacter
    {
        if(someCondition)
        {
            evolvable.IncreaseEvolution(1);
        }
    }
}

Затем, позже, скажем, мы хотим, чтобы персонаж развивался в зависимости от уровня! Фантастика. У нас есть интерфейс ILevellable, который содержит Level, XP и т. Д.

public interface ILevellable
{
    int Level{ get; }
    int MaxLevel{get;}
    int XP {get;}
    bool LevelUp(int numLevels);
}

Мы можем использовать эти данные, чтобы контролировать, когда происходит эволюция на основе изменения уровня. Но вот моя проблема! Мой интерфейс класса обработчика развивается с IEvolvable ... не с ILevellable ... Так что мне делать? Я могу иметь IEvolvable для расширения ILevellable или наоборот ... или я могу создать новый интерфейс, который расширяет IEvolvable и ILevellable. Теперь я также должен изменить свой обработчик evolve, чтобы приспособиться к этим изменениям. Но что произойдет, если мы не хотим, чтобы обработчик эволюции больше учитывал уровень в нашей новой игре? Используете ли вы старый код? Я должен был расширить свой старый код, чтобы включить интерфейс Ilevellable? Это мои вопросы.

public interface ILevelEvolver : ILevellable, IEvolvable
{
}

public class EvolutionHandler2
{
     public ILevelEvolver levelEvolvable;

    public void TryEvolveCharacter
    {
        if(levelEvolvable.Level > 10)
        {
            evolvable.IncreaseEvolution(1);
        }
    }
}

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

Спасибо!

Ответы [ 3 ]

0 голосов
/ 06 сентября 2018

ключевые слова:

  • отделяют то, что отличается от того, что остается прежним
  • один из принципов SOLID: открыт для расширения закрыт для модификации

наконец, в вашем случае будет использоваться стратегия шаблон:

public interface IEvilutionChecker{

    bool AllowEvolution();
}


public class EvolutionCheckerA : IEvilutionChecker{
    private ILevellable levelEvolvable;
    public EvolutionCheckerA(ILevellable levelEvolvable){
        this.levelEvolvable = levelEvolvable;
    }
    public bool AllowEvolution(){
        return levelEvolvable.Level > 10;
    }
}

public class EvolutionCheckerB : IEvilutionChecker{
    private IEvolvable evolvable;
    public EvolutionCheckerB(IEvolvable evolvable){
        this.evolvable = evolvable;
    }
    public bool AllowEvolution(){
        return someCondition;
    }
}

public class EvolutionHandler2
{
    public IEvolvable evolvable; 
    public IEvilutionChecker EvolutionChecker {get;set;};

    public void TryEvolveCharacter
    {
        if(EvolutionChecker.AllowEvolution())  
        {
            evolvable.IncreaseEvolution(1);
        }
    }
}
0 голосов
/ 06 сентября 2018

Интерфейсы не должны расширять друг друга. Держите их отдельно. Также вы должны держать понятия отдельно. При этом EvolutionHandler должен принимать только IEvolable. В методе TryEvolveCharacter вы можете проверить, является ли свойство ILevelable. Посмотрите на код:

class EvolutionHandler
{
    public IEvolable Evolable { get; set; }

    public void TryEvolveCharacter()
    {
        if (Evolable is ILevelable levelable && levelable.Level > 10)
        {
            Evolable.IncreaseEvolution(1);
        }
        else if (someCondition)
        {
            Evolable.IncreaseEvolution(1);
        }
    }
}

так что в будущем, если символ расширяется ILevelable, этот уровень будет считаться, если нет, произойдет какое-то условие.

0 голосов
/ 06 сентября 2018

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

public interface IEvolutionService {
    TryEvolveCharacter(IEvolvable evolvable); 
}

Конкретная реализация может иметь что-то вроде

public void TryEvolveCharacter(IEvolvable evolvable){
        if (evolvable.Level > 10) {
            evolvable.IncreaseEvolution(1);
            ..Maybe do something new that the IEvolvable just exposed but without changing our consumed interface!
        }
}

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

Вы можете сделать это методом для статического класса, хотя это мешает тестируемости, поэтому я бы сказал, рефакторинг и добавление нового сервиса для обработки таких вещей, как service.TryEvolveCharacter (someIEvolvable). Вам все равно придется поддерживать интерфейс в вашей общедоступной службе, но это может быть более управляемым, чем необработанный интерфейс, без абстракций.

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

...