انتقل إلى المحتوى الرئيسي

الفصل 4: لومبوك - تقليل الكود المتكرر

مقدمة​

سؤال: كم عدد أسطر الكود لفئة بسيطة Product؟

بدون لومبوك: ~100 سطر (getters، setters، المنشئون، toString، equals، hashCode)

مع لومبوك: ~10 أسطر

لومبوك = إنتاجية 🚀


ما هو لومبوك؟​

تعريف​

لومبوك هي مكتبة جافا تقوم بتوليد الكود المتكرر تلقائيًا باستخدام التعليقات التوضيحية.

الكود المتكرر = كود ممل ومتكرر 😴

أمثلة:

  • Getters و setters
  • المنشئون
  • toString()
  • equals() و hashCode()

تشبيه​

بدون لومبوك:

أنت طاهٍ يجب أن:

  1. تغسل الخضروات
  2. تقطع الخضروات
  3. تطبخ الطبق
  4. تنظف المطبخ

مع لومبوك:

تقول "أعد لي طبقًا" ويقوم مساعد تلقائيًا بالخطوات 1، 2، و4. تركز أنت على الخطوة 3 (الطبخ).


تثبيت لومبوك​

الخطوة 1: إضافة التبعية Maven​

الملف: pom.xml

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>

ملاحظة: <optional>true</optional> تشير إلى أن لومبوك يُستخدم فقط عند التجميع.


الخطوة 2: تثبيت الإضافة في IDE​

VS Code​

  1. افتح الإضافات (Ctrl+Shift+X أو Cmd+Shift+X)
  2. ابحث عن: "Lombok Annotations Support for VS Code"
  3. قم بتثبيت الإضافة من Gabriel Basilio Brito
  4. أعد تشغيل VS Code

IntelliJ IDEA​

  1. اذهب إلى File → Settings → Plugins
  2. ابحث عن: "Lombok"
  3. قم بتثبيت الإضافة
  4. أعد تشغيل IntelliJ

Eclipse​

  1. قم بتنزيل lombok.jar من projectlombok.org
  2. نفذ: java -jar lombok.jar
  3. اختر تثبيت Eclipse الخاص بك
  4. أعد تشغيل Eclipse

الخطوة 3: التحقق​

أنشئ فئة اختبار:

package com.example.demo.model;

import lombok.Data;

@Data
public class TestLombok {
private String name;
private Integer age;
}

قم بالتجميع:

mvn clean compile

تحقق في وحدة التحكم:

[INFO] BUILD SUCCESS

✅ لومبوك يعمل!


التعليقات التوضيحية الرئيسية​

1. @Data​

تولد تلقائيًا:

  • جميع getters
  • جميع setters
  • toString()
  • equals() و hashCode()
  • منشئ للحقول final

قبل لومبوك​

الملف: model/Product.java

package com.example.demo.model;

import jakarta.persistence.*;

@Entity
@Table(name = "products")
public class Product {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String name;
private Double price;

// منشئ فارغ
public Product() {}

// منشئ مع معلمات
public Product(String name, Double price) {
this.name = name;
this.price = price;
}

// Getters
public Long getId() {
return id;
}

public String getName() {
return name;
}

public Double getPrice() {
return price;
}

// Setters
public void setId(Long id) {
this.id = id;
}

public void setName(String name) {
this.name = name;
}

public void setPrice(Double price) {
this.price = price;
}

// toString()
@Override
public String toString() {
return "Product{" +
"id=" + id +
", name='" + name + '\'' +
", price=" + price +
'}';
}

// equals() و hashCode() (50+ سطر إضافي)
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Product product = (Product) o;
return Objects.equals(id, product.id) &&
Objects.equals(name, product.name) &&
Objects.equals(price, product.price);
}

@Override
public int hashCode() {
return Objects.hash(id, name, price);
}
}

الإجمالي: ~100 سطر


بعد لومبوك​

الملف: model/Product.java

package com.example.demo.model;

import jakarta.persistence.*;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;

@Entity
@Table(name = "products")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String name;
private Double price;
}

الإجمالي: ~15 سطر

التقليل: من 100 سطر إلى 15 سطر! (تقليل بنسبة 85%) 🎉


2. @Getter / @Setter​

تولد getters/setters بشكل فردي.

مثال:

package com.example.demo.model;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;

@Entity
public class Product {

@Id
@GeneratedValue
private Long id;

@Getter @Setter
private String name;

@Getter @Setter
private Double price;
}

ما يعادل:

public String getName() { return name; }
public void setName(String name) { this.name = name; }

public Double getPrice() { return price; }
public void setPrice(Double price) { this.price = price; }

الاستخدام على مستوى الفئة:

@Getter
@Setter
@Entity
public class Product {
private Long id;
private String name;
private Double price;
}

تولد جميع getters و setters لجميع الحقول.


3. @NoArgsConstructor / @AllArgsConstructor​

@NoArgsConstructor​

تولد منشئ بدون معلمات.

@NoArgsConstructor
@Entity
public class Product {
private Long id;
private String name;
private Double price;
}

ما يعادل:

public Product() {}

@AllArgsConstructor​

تولد منشئ مع جميع المعلمات.

@AllArgsConstructor
@Entity
public class Product {
private Long id;
private String name;
private Double price;
}

ما يعادل:

public Product(Long id, String name, Double price) {
this.id = id;
this.name = name;
this.price = price;
}

توليفة شائعة:

@NoArgsConstructor
@AllArgsConstructor
@Entity
public class Product {
private Long id;
private String name;
private Double price;
}

تولد:

  • Product() - منشئ فارغ
  • Product(Long id, String name, Double price) - منشئ كامل

4. @RequiredArgsConstructor​

تولد منشئ مع الحقول final أو @NonNull.

مثال:

@RequiredArgsConstructor
@Entity
public class Product {
private Long id;

@NonNull
private String name;

@NonNull
private Double price;
}

تولد:

public Product(String name, Double price) {
this.name = name;
this.price = price;
}

ملاحظة: id مستثنى لأنه ليس final وليس @NonNull.


5. @Builder​

نمط Builder لإنشاء الكائنات.

مثال:

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class Product {
@Id
@GeneratedValue
private Long id;
private String name;
private Double price;
}

الاستخدام:

Product product = Product.builder()
.name("Laptop")
.price(999.99)
.build();

المزايا:

  • كود قابل للقراءة
  • معلمات مسماة
  • ترتيب مرن

6. @ToString​

تولد تلقائيًا toString().

مثال:

@ToString
@Entity
public class Product {
private Long id;
private String name;
private Double price;
}

ما يعادل:

@Override
public String toString() {
return "Product(id=" + id + ", name=" + name + ", price=" + price + ")";
}

استبعاد الحقول:

@ToString(exclude = {"price"})
@Entity
public class Product {
private Long id;
private String name;
private Double price;
}

النتيجة:

Product(id=1, name=Laptop)

7. @EqualsAndHashCode​

تولد equals() و hashCode().

مثال:

@EqualsAndHashCode
@Entity
public class Product {
private Long id;
private String name;
private Double price;
}

ما يعادل:

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Product product = (Product) o;
return Objects.equals(id, product.id) &&
Objects.equals(name, product.name) &&
Objects.equals(price, product.price);
}

@Override
public int hashCode() {
return Objects.hash(id, name, price);
}

استخدام ID فقط:

@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@Entity
public class Product {
@EqualsAndHashCode.Include
@Id
private Long id;

private String name;
private Double price;
}

✅ ممارسة جيدة للكيانات JPA!


توليفات شائعة​

للكيانات JPA​

التكوين الموصى به:

@Entity
@Table(name = "products")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@NotBlank
private String name;

@Min(0)
private Double price;
}

تولد:

  • Getters و setters
  • منشئ فارغ (مطلوب من JPA)
  • منشئ كامل
  • toString(), equals(), hashCode()

للـ DTOs​

التكوين الموصى به:

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ProductDTO {
private Long id;
private String name;
private Double price;
}

الاستخدام:

ProductDTO dto = ProductDTO.builder()
.id(1L)
.name("Laptop")
.price(999.99)
.build();

للخدمات (حقن التبعيات)​

التكوين الموصى به:

@Service
@RequiredArgsConstructor
public class ProductService {

private final ProductRepository productRepository;

// لا حاجة لمنشئ!

public List<Product> findAll() {
return productRepository.findAll();
}
}

لومبوك تولد تلقائيًا:

public ProductService(ProductRepository productRepository) {
this.productRepository = productRepository;
}

المزايا والعيوب​

المزايا​

✅ أقل كود متكرر (تقليل بنسبة 85%)
✅ كود أكثر قابلية للقراءة (التركيز على منطق العمل)
✅ سهولة الصيانة (لا حاجة لتحديث getters/setters)
✅ زيادة الإنتاجية (تطوير أسرع)
✅ أخطاء أقل (كود مولد تلقائيًا)


العيوب​

❌ اعتماد خارجي (إضافة مكتبة)
❌ يتطلب إضافة IDE (لإكمال الكود)
❌ قد يخفي التعقيد (كود غير مرئي)
❌ منحنى تعلم (للمبتدئين)
❌ تصحيح أكثر تعقيدًا (كود مولد غير مرئي)


أفضل الممارسات​

1. تجنب @Data للكيانات ذات العلاقات​

المشكلة:

@Entity
@Data // انتباه!
public class Order {
@Id
private Long id;

@OneToMany
private List<OrderItem> items;
}

الخطر: toString() قد يسبب حلقات لا نهائية مع العلاقات ثنائية الاتجاه.

الحل:

@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString(exclude = {"items"})
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Order {
@EqualsAndHashCode.Include
@Id
private Long id;

@OneToMany
private List<OrderItem> items;
}

2. استخدام @RequiredArgsConstructor للخدمات​

جيد:

@Service
@RequiredArgsConstructor
public class ProductService {
private final ProductRepository productRepository;
}

سيء:

@Service
public class ProductService {
@Autowired
private ProductRepository productRepository;
}

لماذا؟ حقن المنشئ مفضل (قابلية الاختبار، الثبات).


3. دائمًا الجمع مع @NoArgsConstructor لـ JPA​

JPA يتطلب منشئ فارغ:

@Entity
@Data
@NoArgsConstructor // إلزامي لـ JPA
@AllArgsConstructor
public class Product {
@Id
private Long id;
private String name;
}

ملخص​

التعليقات التوضيحية الأساسية​

التعليق التوضيحييولد
@DataGetters، Setters، toString، equals، hashCode
@Getter / @SetterGetters أو Setters
@NoArgsConstructorمنشئ فارغ
@AllArgsConstructorمنشئ كامل
@RequiredArgsConstructorمنشئ مع final/@NonNull
@Builderنمط Builder
@ToStringtoString()
@EqualsAndHashCodeequals() و hashCode()

تكوين JPA القياسي​

@Entity
@Table(name = "products")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String name;
private Double price;
}

✅ كود حد أدنى، وظيفة قصوى!


الخطوة التالية​

لديك الآن جميع الأدوات النظرية للوحدة 4! انتقل إلى TP4.1 لـ ترحيل تطبيقك إلى PostgreSQL باستخدام لومبوك.

➡️ TP4.1: ترحيل PostgreSQL

⬅️ الفصل السابق: Hibernate DDL

⬅️ العودة إلى الوحدة 4