Статьи

Уроки, извлеченные из реализации Hibernate Core

Hibernate — это проект с открытым исходным кодом. Выполнять мощные объектно-реляционные сопоставления и базы данных запросов с использованием HQL и SQL.

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

Пакет по функции

Пакет за функцией использует пакеты для отражения набора функций. Он помещает все элементы, связанные с одной функцией (и только этой функцией), в один каталог / пакет. Это приводит к упаковкам с высокой когезией и высокой модульностью и с минимальной связью между упаковками. Элементы, которые работают в тесном контакте, расположены рядом друг с другом

Вот хорошая статья, рассказывающая об упаковке по функциям.

Ядро Hibernate содержит множество пакетов, каждый из которых связан с определенной функцией hql, sql и другими.

hibernate1

Связь

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

Вот три ключевых преимущества использования интерфейсов:

  • Интерфейс предоставляет способ определения контракта, который способствует повторному использованию. Если объект реализует интерфейс, то этот объект должен соответствовать стандарту. Объект, который использует другой объект, называется потребителем. Интерфейс — это контракт между объектом и его потребителем.
  • Интерфейс также обеспечивает уровень абстракции, облегчающий понимание программ. Интерфейсы позволяют разработчикам начать говорить об общем поведении кода, не вдаваясь в подробности.
  • Интерфейс обеспечивает низкую связь между компонентами, что облегчает защиту потребителя интерфейса от любых изменений реализации в классах, реализующих интерфейсы.

Давайте найдем все интерфейсы, определенные Hibernate Core, для этого мы используем CQLinq для запроса базы кода.

from  t in Types where t.IsInterface select t

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

Класс A реализует интерфейс IA, который содержит метод Calculate (), а потребительский класс C реализован так

public class C
{
   ….
   public void calculate()
   {
     …..
     m_a.calculate();
     ….
    }
    A m_a;

}

Класс C вместо ссылки на интерфейс IA, он ссылается на класс A, в этом случае мы теряем преимущество низкой связи, и эта реализация имеет два основных недостатка:

  • Если мы решим использовать другую реализацию IA, мы должны изменить код класса C.
  • Если некоторые методы добавляются к A, не существующему в IA, и C использует их, мы также теряем преимущество использования интерфейсов по контракту.

C # представил явную возможность реализации интерфейса для языка, чтобы гарантировать, что метод из IA никогда не будет вызываться из ссылки на конкретные классы, а только из ссылки на интерфейс. Этот метод очень полезен для защиты разработчиков от потери преимуществ использования интерфейсов.

С помощью JArchitect мы можем проверять ошибки такого рода с помощью CQLinq, идея состоит в том, чтобы искать все методы из конкретных классов, используемых непосредственно другими методами.

from m in Methods  where m.NbMethodsCallingMe>0 && m.ParentType.IsClass
 && !m.ParentType.IsThirdParty && !m.ParentType.IsAbstract

let interfaces= m.ParentType.InterfacesImplemented
from i in interfaces where i.Methods.Where(a=>a.Name==m.Name &&
a.ParentType!=m.ParentType).Count()>0
select new { m,m.ParentType,i }

hibernate2

Например, метод getEntityPersister из SessionFactoryImpl, который реализует интерфейс SessionFactoryImplementor, касается этой проблемы.

Давайте искать методы, вызывающие непосредственно SessionFactoryImpl.getEntityPersister.

from m in Methods where m.IsUsing ("org.hibernate.internal.SessionFactoryImpl.getEntityPersister(String)")
select new { m, m.NbBCInstructions }

hibernate3

Такие методы, как SessionImpl.instantiate, вызывают напрямую getEntityPersister вместо передачи по интерфейсу, что лишает преимущества использования интерфейсов. К счастью, ядро ​​hibernate не содержит многих методов, имеющих эту проблему.

Связь с внешними банками
Когда используются внешние библиотеки, лучше проверить, можем ли мы легко заменить стороннюю библиотеку другой, не затрагивая все приложение, есть много причин, которые могут побудить нас изменить стороннюю библиотеку. Другая библиотека может:

— Иметь больше возможностей.
— Более могущественный.
— Более безопасный.

Давайте возьмем пример библиотеки antlr lib, которая использовалась для анализа запросов hql, и представим, что был создан другой синтаксический анализатор, более мощный, чем antlr. Можем ли мы легко изменить antlr новым синтаксическим анализатором?

Чтобы ответить на этот вопрос, давайте посмотрим, какие методы из hibernate используют его напрямую:

    from m in Methods where m.IsUsing ("antlr-2.7.7")
    select new { m, m.NbBCInstructions }

hibernate4

И какие использовали это косвенно:

from m in Projects.WithNameNotIn( "antlr-2.7.7").ChildMethods()
let depth0 = m.DepthOfIsUsing("antlr-2.7.7")
where depth0 > 1 orderby depth0
select new { m, depth0 }

hibernate5

Многие методы используют antlr напрямую, что делает ядро ​​гибернации тесно связанным с ним, и замена antlr другим не является легкой задачей. этот факт не означает, что у нас есть проблема в спящем дизайне, но мы должны быть осторожны при использовании сторонних библиотек и тщательно проверять, должна ли сторонняя библиотека быть слабосвязанной или нет с приложением.

когезия

Принцип единой ответственности гласит, что у класса должна быть одна и только одна причина для изменения. Такой класс называется сплоченным. Высокое значение LCOM обычно указывает на плохо сцепленный класс. Есть несколько показателей LCOM. LCOM принимает свои значения в диапазоне [0-1]. LCOMHS (HS означает «Хендерсон-Селлерс») принимает значения в диапазоне [0-2]. Обратите внимание, что показатель LCOMHS часто считается более эффективным для обнаружения несвязных типов.

Значение LCOMHS выше 1 следует считать тревожным.

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

Давайте искать типы, имеющие много методов и полей.

from t in Types where
(t.Methods.Count() > 40 || t.Fields.Count()>40) && t.IsClass
orderby t.Methods.Count() descending
select new { t, t.InstanceMethods, t.Fields,t.LCOMHS }

hibernate6

Только несколько типов касаются этого запроса, и для всех них LCOMHS меньше 1.

Использование аннотаций

Разработка на основе аннотаций освобождает Java-разработчиков от необходимости громоздкой конфигурации. И дайте нам мощную функцию, чтобы освободить исходный код от стандартного кода. Результирующий код также с меньшей вероятностью содержит ошибки.

Давайте найдем все аннотации, определенные ядром hibernate.

    from t in Types where t.IsAnnotationClass && !t.IsThirdParty select t

hibernate7

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

Заключение
Hibernate Core — хороший пример проектов с открытым исходным кодом, чтобы изучить их, не стесняйтесь заглянуть внутрь.