الفصل 4: لومبوك - تقليل الكود المتكرر
مقدمة
سؤال: كم عدد أسطر الكود لفئة بسيطة Product؟
بدون لومبوك: ~100 سطر (getters، setters، المنشئون، toString، equals، hashCode)
مع لومبوك: ~10 أسطر
لومبوك = إنتاجية 🚀
ما هو لومبوك؟
تعريف
لومبوك هي مكتبة جافا تقوم بتوليد الكود المتكرر تلقائيًا باستخدام التعليقات التوضيحية.
الكود المتكرر = كود ممل ومتكرر 😴
أمثلة:
- Getters و setters
- المنشئون
toString()equals()وhashCode()
تشبيه
بدون لومبوك:
أنت طاهٍ يجب أن:
- تغسل الخضروات
- تقطع الخضروات
- تطبخ الطبق
- تنظف المطبخ
مع لومبوك:
تقول "أعد لي طبقًا" ويقوم مساعد تلقائيًا بالخطوات 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
- افتح الإضافات (
Ctrl+Shift+XأوCmd+Shift+X) - ابحث عن: "Lombok Annotations Support for VS Code"
- قم بتثبيت الإضافة من Gabriel Basilio Brito
- أعد تشغيل VS Code
IntelliJ IDEA
- اذهب إلى File → Settings → Plugins
- ابحث عن: "Lombok"
- قم بتثبيت الإضافة
- أعد تشغيل IntelliJ
Eclipse
- قم بتنزيل
lombok.jarمن projectlombok.org - نفذ:
java -jar lombok.jar - اختر تثبيت Eclipse الخاص بك
- أعد تشغيل 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;
}
ملخص
التعليقات التوضيحية الأساسية
| التعليق التوضيحي | يولد |
|---|---|
@Data | Getters، Setters، toString، equals، hashCode |
@Getter / @Setter | Getters أو Setters |
@NoArgsConstructor | منشئ فارغ |
@AllArgsConstructor | منشئ كامل |
@RequiredArgsConstructor | منشئ مع final/@NonNull |
@Builder | نمط Builder |
@ToString | toString() |
@EqualsAndHashCode | equals() و 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 باستخدام لومبوك.