Сохранение и загрузка объектов без нарушения инкапсуляции

Я хочу сохранять и загружать объекты в базу данных без использования ORM (например, Hibernate).

Допустим, у меня есть следующий класс:

public class Person {
    private int age;

    public void birthday(){
        age++;
    }
 }

Этот класс не предоставляет метод получения, поэтому внутреннее состояние (возраст) инкапсулировано.

Если я хочу сохранить объект, мне нужен метод получения, чтобы сделать следующее:

insert into TABLE_PERSON (id, age) vales (1, person.getAge());

В противном случае мне нужен метод установки для загрузки объекта:

int age = "Select age FROM Person";
person.setAge(age);

Но я не хочу нарушать инкапсуляцию (путем реализации дополнительных методов установки и получения) только для сохранения и загрузки объектов.

Есть ли возможность это сделать? Может быть, какой-то шаблон (на память?) или лучший опыт?

Заранее спасибо!


person Aeon    schedule 23.07.2014    source источник


Ответы (5)


Вы упомянули Мементо. Существует вариант под названием Snapshot, задокументированный Грандом. Вот диаграмма классов UML:

Snapshot pattern

Person будет Originator в шаблоне.

Вы можете сбросить экземпляр Memento в двоичный объект в базе данных.

Обратите внимание, что Memento — это внутренний класс Создателя. createMemento/setMemento — это своего рода одиночный get/set, который может нарушать инкапсуляцию, если вы пурист. Однако пакет информации (Memento), используемый при вызове, представляет собой интерфейс без никаких методов, поэтому гарантируется инкапсуляция состояния Originator's. Это может работать даже с ORM, если вы правильно отобразите его.

Конечно, это кажется большой работой, чтобы избежать get/set. Ваш пример Person/age не совсем реалистичен (не лучшая модель Person). Совершенно нормально раскрывать возраст или дату рождения человека, и раскрытие этого свойства для сохранения объекта было бы в порядке. Инкапсуляция не означает ничего не раскрывать. Это означает, что не выставляйте слишком много деталей.

Например, давайте использовать не age, а Person.birthday. Это может быть сохранено внутри как строка в формате ГГГГ-ММ-ДД или с использованием объекта Date. Такая деталь не будет раскрыта (она будет инкапсулирована). Это также сделано для того, чтобы вы могли изменить его и не затрагивать клиентов вашего класса. Все, что вы скрываете, может быть изменено без негативного влияния на клиентов.

Открывая день рождения человека, вы говорите "Я беру на себя риск того, что у человека всегда будет день рождения". Эта часть открыта, и поэтому ее будет трудно изменить, не нарушая работы клиентов.

Редактировать после комментариев

Публичные методы (например, save и load) в Person могут заботиться об операции сохранения/загрузки базы данных. У них будет доступ к закрытым полям (age) и они смогут выполнять свою работу. Вы сказали, что не используете ORM, поэтому просто делаете это сами.

person Fuhrmanator    schedule 24.07.2014
comment
Я согласен с вами в том, что объект сувенира скрывает состояние создателя, поэтому инкапсуляция не нарушается. И я согласен, что я мог бы сохранить объект сувенира как двоичный объект в базе данных. Есть только одна последняя вещь, которую я бы пропустил в этом решении: я хотел бы выполнять SQL-запросы к объектам, хранящимся в базе данных, что было бы невозможно, потому что объекты не отображаются в столбцах таблицы. Я не указал это в своем вопросе, извините за это. - person Aeon; 25.07.2014
comment
Select memento from person подойдет, верно @Aeon? В каждой таблице объектов будет один столбец для сувенира. - person Fuhrmanator; 25.07.2014
comment
Да, верно, но я имею в виду кое-что другое: я хотел бы выполнять такие операторы, как Select Max(age) from person. - person Aeon; 25.07.2014
comment
Если вам нужно знать возраст для SQL-запросов, то почему вы вообще пытаетесь его скрыть? - person Fuhrmanator; 25.07.2014
comment
Я пытаюсь скрыть возраст (или другие атрибуты) от других объектов Java, потому что я хочу обеспечить инкапсуляцию. Мне нужно получить доступ к атрибутам через SQL для проведения различных анализов. SQL-запросы находятся вне java-приложения. - person Aeon; 25.07.2014
comment
В исходном шаблоне сувенира (описанном GOF) говорится, что сувенир не раскрывает свойства/структуры данных другим объектам, поэтому он не подходит для сохранения в столбцах базы данных. Я предполагаю, что мы говорим здесь о том, что это будет так, чтобы реализация репозитория могла сохранять свойства в столбцах базы данных. Это действительно раскрывает внутренности объекта, однако для меня важно то, что только создатель может создать сувенир/моментальный снимок (поскольку это частный класс создателя), тем самым предотвращая состояние устанавливается без прохождения через публичных членов. - person David Masters; 14.11.2020

Аллен Холуб написал несколько статей именно на эту тему. Он предлагает Builder и то, что он называет паттерном Reverse Builder.

Как это будет работать в вашем случае, Person будет нести ответственность за создание представления о себе. Другими словами, вы определяете интерфейсы PersonImporter и PersonExporter для перемещения данных в объект Person и из него. Эти классы в основном являются частью дизайна Person. Так:

interface PersonImporter {

    public int getAge();

    public String getId();
}

interface PersonExporter {

    public void setDetails(String id, int age);

}

class Person {

    private int age;
    private String id;

    public Person(PersonImporter importer) {
        age = importer.getAge();
        id = importer.getId();
    }

    public void export(PersonExporter exporter) {
        exporter.setDetails(id, age);
    }

}

Это не устраняет геттеры и сеттеры полностью, а контролирует их с помощью интерфейсов.

статья Holub

person Weltschmerz    schedule 12.02.2015

Это то, что я думаю, может быть не идеально.

Инкапсуляция делается для сокрытия данных. Возможно, вы просто не хотите, чтобы кто-то установил неправильное значение атрибута age.

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

Если вы создаете файл с классом-оболочкой public, скажем, PersonWrapper, который предоставляет геттер и сеттер для возраста. Но эти геттеры и сеттеры могут иметь желаемую логику проверки, например, какие значения могут быть установлены для age, кто может и т. д. В том же файле класс Person может быть определен как private, но с простыми геттерами и сеттерами для параметра age. Ваш PersonWrapper должен использовать геттер и сеттер класса Person только при некоторых предопределенных условиях.

Таким образом, вы можете иметь лучшую инкапсуляцию.

person Juned Ahsan    schedule 23.07.2014

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

person Sebin Eapen George    schedule 24.07.2014

Я действительно не понимаю, как использование геттеров/сеттеров нарушает инкапсуляцию. Геттеры и сеттеры уважают инкапсуляцию. На самом деле, они являются средством для достижения этого.

person user3806044    schedule 23.07.2014
comment
comment
Вы минусуете меня и связываете меня с вопросом, по которому первая строка верхнего ответа согласна со мной, говоря, что геттеры и сеттеры не нарушают инкапсуляцию, они ее обеспечивают.? Пожалуйста, объясни. - person user3806044; 24.07.2014
comment
Может не я минусовал? Ссылка на этот вопрос показывает, что ваша позиция противоречива (прочитайте комментарии к верхнему ответу). Также прочитайте мой комментарий к вопросу по ссылке. - person Fuhrmanator; 24.07.2014
comment
В таком случае прошу прощения за поспешность с выводами. Но я был бы признателен, если бы те, кто проголосовал за меня, действительно нашли время, чтобы объяснить, почему. Для меня инкапсуляция — это предоставление данных только через контролируемые интерфейсы, и это именно то, что достигается с помощью геттеров и сеттеров. - person user3806044; 24.07.2014
comment
Каждый раз, когда я получал минус на stackoverflow, я использовал это как возможность учиться (открыть свой разум и рассмотреть альтернативы моей точке зрения). В обсуждении по ссылке, которую я разместил, есть много комментариев, которые должны ответить, почему. - person Fuhrmanator; 25.07.2014
comment
Я совершенно готов открыть свой разум, как вы выразились, и рассмотреть альтернативы. Глядя на эти комментарии, я не вижу, чтобы я сказал что-то особенно возмутительное. Трудно обратиться к альтернативной точке зрения, когда мне остается гадать, какой она может быть. - person user3806044; 25.07.2014
comment
Я должен добавить одну вещь. Когда я говорю об использовании геттеров и сеттеров, я имею в виду их использование с вашим собственным кодом. Вы знаете свой собственный код и знаете, как его следует показывать. Добавление геттеров и сеттеров в чужой код, а не выяснение их существующих интерфейсов, — это другое дело. - person user3806044; 25.07.2014
comment
Вам нужно больше репутации, чтобы принять это в чат. - person Fuhrmanator; 25.07.2014
comment
Да, к сожалению, я редко пишу на этом сайте, поэтому не знаю, как лучше продолжить обсуждение. На мой взгляд, SO в этом отношении является излишне ограничительным. - person user3806044; 25.07.2014
comment
В моем примере мне нужно было бы реализовать геттер для каждого отдельного атрибута класса Person (давайте представим, что он имеет гораздо больше, чем просто возраст). Так что, на мой взгляд, все внутреннее состояние объекта будет выставлено наружу. - person Aeon; 25.07.2014
comment
Если все данные должны быть раскрыты, пусть будет так. Но дело в том, что он должен быть доступен только через контролируемые интерфейсы (т. е. геттеры и сеттеры). - person user3806044; 25.07.2014
comment
Я согласен с вами, что если данные должны быть раскрыты, то раскрытие их через контролируемые интерфейсы — правильный путь. Дело в том, что необходимость/причина раскрытия данных — это всего лишь уровень сохраняемости. Таким образом, каждый объект, который необходимо сохранить, должен предоставлять все данные. - person Aeon; 25.07.2014
comment
Я не понимаю, почему наличие слоя постоянства, задействованного в этом случае, должно иметь какое-либо значение. Даже если каждый отдельный атрибут, который вы хотите сохранить, должен быть представлен через интерфейс, что, я не уверен, вообще необходимо, что с того? Все еще не уверен, как это нарушает инкапсуляцию. - person user3806044; 25.07.2014