Ну, во-первых, я думаю, что ваша основная проблема ошибочна.По моему опыту, при проектировании архитектуры для «простоты использования», несмотря на то, что на нее приятно смотреть со всей их инкапсулированной функциональностью, она, как правило, сильно взаимозависима и жестка.По мере роста приложения, построенного на таком принципе, вы столкнетесь с серьезными проблемами с зависимостями (классы в конечном итоге становятся все более и более зависимыми от все большего и косвенно зависимыми, в конечном счете, от всего в вашей системе.) Это приводит к настоящим кошмарам обслуживания, которыеВыдвиньте преимущества «простоты использования», которые вы можете получить.
Двумя наиболее важными правилами архитектуры являются Разделение интересов и Одиночная ответственность .Эти два правила диктуют такие вещи, как разделение инфраструктурных задач (доступ к данным, разбор) и бизнес-задач (поиск фильмов) и обеспечение того, чтобы каждый класс, который вы пишете, отвечал только за одну вещь (представление информации о фильме, поиск отдельных фильмов).
Ваша архитектура, хотя и небольшая, уже нарушила обе функции.Ваш класс «Кино», хотя он и элегантен, сплочен и прост в использовании, сочетает в себе две обязанности: представление информации о фильме и обслуживание поиска фильмов.Эти две обязанности должны быть в разных классах:
// Data Contract (or Data Transfer Object)
public class Movie
{
public Image Poster { get; set; }
public string Title { get; set; }
public DateTime ReleaseDate { get; set; }
public string Rating { get; set; }
public string Director { get; set; }
public List<string> Writers { get; set; }
public List<string> Genres { get; set; }
public string Tagline { get; set; }
public string Plot { get; set; }
public List<string> Cast { get; set; }
public string Runtime { get; set; }
public string Country { get; set; }
public string Language { get; set; }
}
// Movie database searching service contract
public interface IMovieSearchService
{
Movie FindMovie(string Title);
Movie FindKnownMovie(string ID);
}
// Movie database searching service
public partial class MovieSearchService: IMovieSearchService
{
public Movie FindMovie(string Title)
{
Movie film = new Movie();
Parser parser = Parser.FromMovieTitle(Title);
film.Poster = parser.Poster();
film.Title = parser.Title();
film.ReleaseDate = parser.ReleaseDate();
//And so an so forth.
}
public Movie FindKnownMovie(string ID)
{
Movie film = new Movie();
Parser parser = Parser.FromMovieID(ID);
film.Poster = parser.Poster();
film.Title = parser.Title();
film.ReleaseDate = parser.ReleaseDate();
//And so an so forth.
}
}
Это может показаться тривиальным, однако отделение поведения от ваших данных может стать критическим по мере роста системы.Создавая интерфейс для службы поиска фильмов, вы обеспечиваете развязку и гибкость.Если вам по какой-либо причине необходимо добавить другой тип службы поиска фильмов, который предоставляет те же функции, вы можете сделать это, не нарушая своих потребителей.Тип данных Movie можно использовать повторно, ваши клиенты привязываются к интерфейсу IMovieSearchService, а не к конкретному классу, что позволяет взаимозаменять реализации (или использовать несколько реализаций одновременно). Лучше всего поместить интерфейс IMovieSearchService и тип данных Movie в отдельныйпроект, чем класс MovieSearchService.
Вы сделали хороший шаг, написав класс анализатора и не разбирая синтаксический анализ в функциях поиска фильмов.Это соответствует правилу разделения интересов.Тем не менее, ваш подход приведет к трудностям.С одной стороны, он основан на статических методах, которые очень негибки.Каждый раз, когда вам нужно добавить новый тип синтаксического анализатора, вы должны добавить новый статический метод и обновить любой код, который должен использовать этот конкретный тип синтаксического анализа.Лучшим подходом является использование силы полиморфизма и статической канавы:
public abstract class Parser
{
public abstract IEnumerable<Movie> Parse(string criteria);
}
public class ByTitleParser: Parser
{
public override IEnumerable<Movie> Parse(string title)
{
// TODO: Logic to parse movie information by title
// Likely to return one movie most of the time, but some movies from different eras may have the same title
}
}
public class ByActorParser: Parser
{
public override IEnumerable<Movie> Parse(string actor)
{
// TODO: Logic to parse movie information by actor
// This one can return more than one movie, as an actor may act in more than one movie
}
}
public class ByIdParser: Parser
{
public override IEnumerable<Movie> Parse(string id)
{
// TODO: Logic to parse movie information by id
// This one should only ever return a set of one movie, since it is by a unique key
}
}
Наконец, еще один полезный принцип - внедрение зависимостей.Вместо того, чтобы напрямую создавать новые экземпляры ваших зависимостей, абстрагируйте их создание с помощью чего-то вроде фабрики и вставьте ваши зависимости и фабрики в службы, которым они нужны:
public class ParserFactory
{
public virtual Parser GetParser(string criteriaType)
{
if (criteriaType == "bytitle") return new ByTitleParser();
else if (criteriaType == "byid") return new ByIdParser();
else throw new ArgumentException("Unknown criteria type.", "criteriaType");
}
}
// Improved movie database search service
public class MovieSearchService: IMovieSearchService
{
public MovieSearchService(ParserFactory parserFactory)
{
m_parserFactory = parserFactory;
}
private readonly ParserFactory m_parserFactory;
public Movie FindMovie(string Title)
{
var parser = m_parserFactory.GetParser("bytitle");
var movies = parser.Parse(Title); // Parse method creates an enumerable set of Movies that matched "Title"
var firstMatchingMovie = movies.FirstOrDefault();
return firstMatchingMovie;
}
public Movie FindKnownMovie(string ID)
{
var parser = m_parserFactory.GetParser("byid");
var movies = parser.Parse(Title); // Parse method creates an enumerable set of Movies that matched "ID"
var firstMatchingMovie = movies.FirstOrDefault();
return firstMatchingMovie;
}
}
Эта улучшенная версия имеет несколько преимуществ.С одной стороны, он не отвечает за создание экземпляров ParserFactory.Это позволяет использовать несколько реализаций ParserFactory.В самом начале вы можете искать только в IMDB.В будущем, возможно, вы захотите выполнить поиск на других сайтах, и могут быть предоставлены альтернативные парсеры для альтернативных реализаций интерфейса IMovieSearchService.