Циклическая зависимость: IVisitor и Datas
Недавно я спросил о циклической зависимости. Ответом было посвятить проект по интерфейсам (MyProject.Abstractions
). Теперь этот проект является причиной другой циклической зависимости с шаблоном Visitor.
namespace MyProject.Abstractions
{
public interface ICharacter { }
public interface ICharacterVisitor
{
// References MyProject.Characters
void Visit(Warrior warrior);
void Visit(Wizard wizard);
}
}
namespace MyProject.Characters
{
// References MyProject.Abstractions
public abstract class CharacterBase : ICharacter { }
public class Warrior : CharacterBase { }
public class Wizard : CharacterBase { }
}
Означает ли это, что мой ICharacterVisitor должен быть в моем MyProject.Characters
проект? Я посвящаю все это решение моим тренировкам SOLID.
2 ответа
Посетитель - это инструмент, который применяется к существующей структуре, но не является ее частью. Поэтому я бы отделил посетителя от данных, которые вы посещаете.
namespace MyProject.Abstractions.Characters
{
public interface ICharacter { }
}
using MyProject.Abstractions.Characters;
namespace MyProject.Characters
{
public abstract class CharacterBase : ICharacter { }
public class Warrior : CharacterBase { }
public class Wizard : CharacterBase { }
}
using MyProject.Abstractions.Characters;
using MyProject.Characters;
namespace MyProject.Abstractions.Visitors
{
public interface ICharacterVisitor
{
// References MyProject.Characters
void Visit(Warrior warrior);
void Visit(Wizard wizard);
}
}
using MyProject.Abstractions.Characters;
using MyProject.Abstractions.Visitors
using MyProject.Characters;
namespace MyProject.Visitors
{
// Concrete visitors here
}
Вам не обязательно иметь отдельный проект для каждого пространства имен. Материал посетителя может быть в том же проекте, что и MyProject.Characters
, SOLID - это логическая организация кода, а не физическая. В этом ответе на преимущества нескольких проектов и One Solution перечислены веские причины наличия нескольких проектов.
По своей природе шаблон посетителя имеет тенденцию вводить циклическую зависимость - интерфейс посетителя должен знать обо всех различных классах, с которыми он работает, и каждый из них должен каким-то образом знать, как вызвать правильный метод для посетителя, что означает знание посетителя. учебный класс.
Вы можете сломать эту циклическую зависимость, выполнив что-то вроде:
// Abstractions project
interface ICharacter
{
string Name { get; }
}
interface IWarrior : ICharacter
{
void Attack();
}
interface IWizard : ICharacter
{
void CastSpell();
}
interface IVisitor
{
void Visit(IWarrior w);
void Visit(IWizard w);
}
// implementations project
abstract class CharacterBase : ICharacter
{
public string Name { get; }
public abstract void Accept(IVisitor v);
}
class Warrior : CharacterBase, IWarrior
{
public void Attack()
{
// do warrior things
}
public override void Accept(IVisitor v)
{
v.Visit(this);
}
}
class Wizard : CharacterBase, IWizard
{
public void CastSpell()
{
// do wizardly things
}
public override void Accept(IVisitor visitor)
{
visitor.Visit(this);
}
}
Предполагая, что вам действительно нужен класс CharacterBase для хранения какой-либо общей функциональности / свойств между символами.
Стоит ли делать вызов, который вам придется сделать, так как характер шаблона Visitor является реальной проблемой - насколько вероятно, что вы добавите больше типов символов в будущем? Какова вероятность того, что вы добавите больше посетителей? Есть ли у вас способ, которым каждый тип персонажа может решить для себя, какие действия ему необходимо предпринять в определенных сценариях?