Собственно вопрос может породить холивары, но я в данном случае не стремлюсь докопаться до истины. Допустим есть класс работающий с БД, ему нужно получить настройки, можно передать двумя способами
Первый вариант
DbConnector(ip, host, port);
В данном случае имеем преимущества
1. Видим какие параметры использует класс и что ему нужно. Класс обладает относительной «независимостью» от других классов
2. Проще тестировать, т.к. ip, host, port - простые типы и можно проверить его работу без всяких заглушек.
Недостатки
1. Если вдруг появится потребность в новом параметре, например имя БД, нужно переписывать интерфейсы.
2. Опять же, необходимо помнить последовательность передачи параметров.
4. Увеличение кол-ва передаваемых параметров, что усложняет восприятие класса.
Второй вариант
Создаем класс DbSettings и передаем экземляр классу, DbConnector
DbConnector(DbSettings settings_);
Преимущества
1. Код получается более лаконичным
2. Опять же, проще модифицировать
Недостатки
1. Сложней тестировать, т.к. надо для тестирования создавать объект DbSettings;
2. Сложней для анализа, т.к. для того что бы понять откуда берутся настройки, придется вообще пробираться в класс DbSettings.
Недавно читал книгу по проектированию на Java так там первый вариант описывался как более предпочтительный, сегодня уточню авторство
1. Если вдруг появится потребность в новом параметре, например имя БД, нужно переписывать интерфейсы
язык не допускает перегрузку? нельзя одновременно иметь методы DbConnector(ip, host, port), DbConnector(ip, host, port, dbname), DbConnector(settings) ?
2. Опять же, необходимо помнить последовательность передачи параметров
для этого IDE существует. Так-то для любой функции нужно «помнить последовательность», или - языкозависимо - писать нечто вроде DbConnector(ip => «127.0.0.1», host => «localhost», port =>«5432»)
4. Увеличение кол-ва передаваемых параметров, что усложняет восприятие класса
в разумных пределах - нет. Главное, не доводить до уровня winapi, который «у функции 16 параметров, 10 обязаны быть NULL, три зарезервированы, один - структура с 21 полем»
Ок, в общем понял. Оба варианта имеют право на жизнь, в зависимости от ситуации, что касается перегрузки ф-ий, как и параметров по умолчанию, тут на мой субъективный взгляд код проще писать, но сложней затем читать и поддерживать, т.к. логика работы у перегруженных ф-ий часто имеет серьезные отличие, но опять же, все это мое субъективное мнене. C get норм, но опять же, если нужно установить к примеру 3 параметра, получится
Недостатки 1. Сложней тестировать, т.к. надо для тестирования создавать объект DbSettings;
Его не нужно тестировать, это пассивный объект-контейнер без логики.
Сложней для анализа, т.к. для того что бы понять откуда берутся настройки, придется вообще пробираться в класс DbSettings.
Наоборот, плюс, т.к. есть одно место где можно описать как пользоваться настройками и мб даже нариовать pretty-printer содержимого. Это же можно сделать и рядом с кодом метода, но там обычно можно рассказать о чём-то другом.
Второй вариант в нормальных языках всегда можно свести к первому если очень нужно
DbConnector({ ip, host, port });
Нужно делать отдельный объект или нет в основном зависит от кол-ва настроек: если их много или планируется увеличение, то нужно делать объект. Также, если планируется хранить настройки и передать из модуля в модуль, то тоже лучше засовывать в объект чтобы не разводить бесполезных copy + paste из передачи мн-ва аргументов через ф-ии.
В общем случае, естественно, первый вариант + перегрузки и аргументы по умолчанию. Но если аргументов овердофига, и/или из них овердофига опциональных то имеет смысл сделать второе, причём обычно так:
Это два самых худших гогнокодо-варианта из всех возможных.
Первый заставляет держать состояние приготовления настроек внутри коннектора тем самым в одном объекте скрещиввая две сущности (коннектору настройки могут быть не нужны вне конструктора).
И оба варианта это один из классических примеров ООП головного мозга, а именно нагромождение сеттеров/геттеров на пустом месте.
анон, ты офигел builder ООП головного мозга называть когда он тут решает все проблемы ТС, от именования и последовательности, до неопределенности количества параметров?
а вариант DbConnector(const string &url) как основной уже совсем не в моде? :-)
DbConnector(ip,host,port), DbConnector(DbSettings &) навязывают лишние сущности. Первый подразумевает и требует сетевые соединения. Второй требует от пользователя уметь оперировать ещё одним классом.
Никаких плюсов во втором варианте не вижу. Допустим, ты читаешь код. Если написано, как в первом варианте, то все сразу понятно. Если как во втором - то лезешь, ищешь определение этих DbSettings, а когда находишь, то понимаешь, что имелся в виду собственно первый вариант. Кроме того, я не понимаю, как во втором случае код может быть «более лаконичным», когда добавляется плюс один класс.
В случае C++ никакой разницы не будет (если, кончено, ты не нагородишь виртуальных методов).
И в том и в другом случае ты будешь ломать ABI в случае изменений.
Если посмотреть с позиций абстрактного проектирования, то тоже никакой разницы: ну получишь ты сильную межмодульную связь между DbConnector и DbSettings... Оно тебе будет легче?
С клиентской точки зрения — вообще похрен. Тебе нужен будет пул или хотя бы фабрика уровня ШайтанМашнина.ДайМнеСоединениеСБД(), а сигнатура конкретных конструкторов будет где-то очень глубоко, если не «по приглашениям».
Если посмотреть с позиций абстрактного проектирования, то тоже никакой разницы: ну получишь ты сильную межмодульную связь между DbConnector и DbSettings... Оно тебе будет легче?
О как, т.е. вариант с DbSettings это сильное связывание, что-то долго размышлял над этим термином, а сейчас вдруг понял, что в виду имеют.
анон, ты офигел builder ООП головного мозга называть когда он тут решает все проблемы ТС, от именования и последовательности, до неопределенности количества параметров?
Глаза открой и посмотри что за чушь советуешь. Его DbConnector это не фабрика соединений, а объект с состоянием соединения и API для работы с базой. В твоём варианте нужно сделать ещё один класс который после connect() вернёт DbConnector и это в данном случае overengineering.
Не, так-то я согласен что в большей части практических случаев это всего лишь костыль для языков без поддержки нормальных именованных параметров.
Разве в яве нельзя сделать поля класса public и работать с ними как со структурой? В совокупности с final полями и перегрузкой конструктора можно отлично обойтись без геттеров/сеттеров. Мой вариант выглядит так
my $conf = {
type => 'mysql',
name => 'test',
user => 'user',
pass => 'pass',
host => '127.0.0.1', # из дефолта
port => 3306, # из дефолта
opts => { # из дефолта
mysql_enable_utf8 => 1,
AutoCommit => 1,
},
};
# ^^^ берётся из конфига (сериализованный yaml), мержится с дефолтом
# хэш выше - не обычный, не bless()ed
my $conf->{dsn} = sprintf "dbi:%s:dbname=%s;host=%s;port=%d",
$conf->{type}, $conf->{name}, $conf->{host}, $conf->{port};
my $dbh = DBI->connect($conf->{dsn}, $conf->{user}, $conf->{pass}, $conf->{opts});
# ... и далее работаем с $dbh
Вообще я на си++ пишу, просто книгу по проектированию встретил на яве. Вот мне понравилась идея с проверкой валидности параметров объектом Settings, т.к. всю логику проверок можно инкапсулировать в него и скрыть от пользователя. Но тут опять возникает проблема, значительное количество проверок это увеличение скорости отладки т.к. проблемы либо не возникают вообще, либо выявляются на ранних стадиях. Остается найти компромис между увеличением объема кода и как следствие сложностью.
Подводя итог, в принципе не вижу смысла медитировать над этим вопросм дизайна. Оба варианта получается приемлемы, зачем я все это спрашиваю, мне достался крупный проект на доработку, который уже довел до ручки пару программистов, подобного рода детали для меня очень важны и во всех случаях планирую придерживаться правила - «чем код проще читать и анализировать, тем лучше»
Вот мне понравилась идея с проверкой валидности параметров объектом Settings, т.к. всю логику проверок можно инкапсулировать в него и скрыть от пользователя
Логика проверок и так скрыта от пользователя - её делает либо Settings, либо коннектор. Всю логику проверок засунуть в Settings нельзя т.к. существуют общие проверки, которые можно засунуть в Settings, и зависимые от поведения коннектора. Последние нельзя засунуть в Settings.
Разве в яве нельзя сделать поля класса public и работать с ними как со структурой? В совокупности с final полями и перегрузкой конструктора можно отлично обойтись без геттеров/сеттеров. Мой вариант выглядит так
Многословное уродство. Chainable setters намного лучше.
Отнюдь это позволяет работать с изменяемым кол-вом папаметров,которые можно структурировать и проверять как вам хочется без необходимости лезть в основной класс... Ну это мое видение