الفصل 2: تمارين إعادة هيكلة الكود
تحاكي هذه التمارين مواقف مقابلة حقيقية حيث يتم عرض كود سيء عليك ويُطلب منك إعادة هيكلته. يتضمن كل تمرين الكود الإشكالي، الحل، وشرح واضح لما تم تحسينه.
التمرين 1 — إزالة If المتداخلة (Guard Clauses)
"هذا الكود صعب القراءة. أعد هيكلته."
الكود السيء
public void processOrder(Order order) {
if (order != null) {
if (order.getStatus().equals("ACTIVE")) {
if (order.getItems() != null && !order.getItems().isEmpty()) {
if (order.getCustomer() != null) {
double total = 0;
for (Item item : order.getItems()) {
total += item.getPrice() * item.getQuantity();
}
order.setTotal(total);
emailService.sendConfirmation(order.getCustomer().getEmail(), total);
}
}
}
}
}
المشكلة : "هرم العذاب" — تجعل if المتداخلة الكود غير قابل للقراءة وصعب الصيانة.
الكود المعاد هيكلته
public void processOrder(Order order) {
if (order == null) return;
if (!"ACTIVE".equals(order.getStatus())) return;
if (order.getItems() == null || order.getItems().isEmpty()) return;
if (order.getCustomer() == null) return;
double total = calculateTotal(order.getItems());
order.setTotal(total);
emailService.sendConfirmation(order.getCustomer().getEmail(), total);
}
private double calculateTotal(List<Item> items) {
return items.stream()
.mapToDouble(i -> i.getPrice() * i.getQuantity())
.sum();
}
ما تم فعله
| التقنية | الشرح |
|---|---|
| Guard clauses | return في بداية الطريقة تزيل الحالات غير الصالحة مبكرًا |
| Extract method | تم استخراج calculateTotal() في طريقة خاصة بها |
| Stream API | استبدال الحلقة for بخط أنابيب وظيفي |
| "ACTIVE".equals() | عكس الاستدعاء لتجنب NullPointerException |
"I use guard clauses to handle edge cases early and keep the happy path unindented. I also extract helper methods to improve readability and testability."
التمرين 2 — استبدال If/Else بنمط Strategy
"يجب أن يدعم هذا الكود أنواع دفع جديدة بسهولة. أعد هيكلته."
الكود السيء
public double calculateFee(String paymentType, double amount) {
if (paymentType.equals("CREDIT_CARD")) {
return amount * 0.03;
} else if (paymentType.equals("PAYPAL")) {
return amount * 0.025;
} else if (paymentType.equals("BANK_TRANSFER")) {
return amount * 0.01;
} else if (paymentType.equals("CRYPTO")) {
return amount * 0.005;
} else {
throw new IllegalArgumentException("Unknown: " + paymentType);
}
}
المشكلة : كل نوع دفع جديد = تعديل هذه الطريقة. ينتهك مبدأ Open/Closed (مفتوح للتمديد، مغلق للتعديل).
الكود المعاد هيكلته
interface FeeCalculator {
double calculate(double amount);
}
class CreditCardFee implements FeeCalculator {
public double calculate(double amount) { return amount * 0.03; }
}
class PaypalFee implements FeeCalculator {
public double calculate(double amount) { return amount * 0.025; }
}
// Registry مع lambdas للحالات البسيطة
Map<String, FeeCalculator> calculators = Map.of(
"CREDIT_CARD", new CreditCardFee(),
"PAYPAL", new PaypalFee(),
"BANK_TRANSFER", amount -> amount * 0.01,
"CRYPTO", amount -> amount * 0.005
);
public double calculateFee(String type, double amount) {
FeeCalculator calc = calculators.get(type);
if (calc == null) throw new IllegalArgumentException("Unknown: " + type);
return calc.calculate(amount);
}
ما تم فعله
| التقنية | الشرح |
|---|---|
| Strategy Pattern | كل نوع دفع يتم تغليفه في فئة/لامبدا خاصة به |
| Open/Closed Principle | إضافة نوع جديد = إضافة سطر واحد في Map |
| Lambdas | للحالات البسيطة، نستخدم لامبدا بدلاً من الفئات |
| Registry | تعمل Map كمسجل للعثور على الحاسبة الصحيحة |
"I replaced the if/else chain with a Strategy Pattern. Adding a new payment type is now a single line in the registry, without modifying existing code — that's the Open/Closed Principle."
التمرين 3 — تفكيك God Class (SRP)
"هذه الفئة تقوم بالكثير من الأشياء. أعد هيكلتها."
الكود السيء
public class UserService {
public User createUser(String name, String email) {
// Validation
if (name == null || email == null) throw new IllegalArgumentException();
if (!email.contains("@")) throw new IllegalArgumentException("Bad email");
// Création
User user = new User(name, email);
// Persistance directe
String sql = "INSERT INTO users (name, email) VALUES (?, ?)";
jdbcTemplate.update(sql, name, email);
// Email
String subject = "Welcome!";
String body = "Hello " + name;
emailSender.send(email, subject, body);
// Logging
log.info("User created: " + email);
return user;
}
}
المشكلة : هذه الفئة تقوم بأربع أشياء: التحقق، الحفظ، إرسال البريد الإلكتروني، وتسجيل الدخول. ينتهك مبدأ المسؤولية الواحدة.
الكود المعاد هيكلته
// 1. Validation
class UserValidator {
void validate(String name, String email) {
Objects.requireNonNull(name, "name required");
Objects.requireNonNull(email, "email required");
if (!email.contains("@")) throw new IllegalArgumentException("Bad email");
}
}
// 2. Persistance
@Repository
class UserRepository {
void save(User user) {
jdbcTemplate.update(
"INSERT INTO users (name, email) VALUES (?, ?)",
user.getName(), user.getEmail()
);
}
}
// 3. Notification
@Service
class WelcomeEmailService {
void sendWelcome(User user) {
emailSender.send(user.getEmail(), "Welcome!", "Hello " + user.getName());
}
}
// 4. Orchestration (la seule responsabilité du Service)
@Service
public class UserService {
private final UserValidator validator;
private final UserRepository repo;
private final WelcomeEmailService welcomeEmail;
@Transactional
public User createUser(String name, String email) {
validator.validate(name, email);
User user = new User(name, email);
repo.save(user);
welcomeEmail.sendWelcome(user);
return user;
}
}
ما تم فعله
| التقنية | الشرح |
|---|---|
| Single Responsibility | كل فئة لها سبب واحد للتغيير |
| تفكيك | Validator، Repository، EmailService، Service (المنسق) |
| Testabilité | يمكن اختبار كل مكون بشكل مستقل |
| Maintenabilité | تغيير منطق البريد الإلكتروني لا يؤثر على الحفظ |
"I applied the Single Responsibility Principle. The UserService now only orchestrates — validation, persistence, and notification are each in their own class. This makes each piece independently testable and maintainable."
التمرين 4 — استبدال الثوابت السحرية
"هذا الكود يستخدم أرقام سحرية في كل مكان. أعد هيكلته."
الكود السيء
public double calculateShipping(double weight, int zone) {
if (zone == 1) {
if (weight <= 5) return 4.99;
else if (weight <= 20) return 9.99;
else return 14.99;
} else if (zone == 2) {
if (weight <= 5) return 7.99;
else if (weight <= 20) return 14.99;
else return 24.99;
} else {
return 29.99;
}
}
الكود المعاد هيكلته
public enum ShippingZone {
LOCAL(1, Map.of(
WeightCategory.LIGHT, 4.99,
WeightCategory.MEDIUM, 9.99,
WeightCategory.HEAVY, 14.99
)),
NATIONAL(2, Map.of(
WeightCategory.LIGHT, 7.99,
WeightCategory.MEDIUM, 14.99,
WeightCategory.HEAVY, 24.99
));
private final int code;
private final Map<WeightCategory, Double> rates;
public double getRate(double weight) {
return rates.get(WeightCategory.fromWeight(weight));
}
}
enum WeightCategory {
LIGHT(0, 5), MEDIUM(5, 20), HEAVY(20, Double.MAX_VALUE);
static WeightCategory fromWeight(double weight) {
return Arrays.stream(values())
.filter(c -> weight > c.min && weight <= c.max)
.findFirst()
.orElse(HEAVY);
}
}
// Utilisation
public double calculateShipping(double weight, ShippingZone zone) {
return zone.getRate(weight);
}
"I replaced magic numbers with enums and lookup tables. The code is now self-documenting and adding a new zone is just adding an enum value."
التمرين 5 — استبدال الوراثة بالتكوين
"هذه التسلسل الهرمي للفئات جامد. أعد هيكلته."
الكود السيء
class Animal {
void eat() { System.out.println("Eating"); }
}
class FlyingAnimal extends Animal {
void fly() { System.out.println("Flying"); }
}
class SwimmingAnimal extends Animal {
void swim() { System.out.println("Swimming"); }
}
// Problème : un canard vole ET nage. Héritage multiple impossible en Java.
الكود المعاد هيكلته (التكوين)
interface Behavior {
void perform();
}
class FlyBehavior implements Behavior {
public void perform() { System.out.println("Flying"); }
}
class SwimBehavior implements Behavior {
public void perform() { System.out.println("Swimming"); }
}
class Animal {
private final String name;
private final List<Behavior> behaviors;
Animal(String name, Behavior... behaviors) {
this.name = name;
this.behaviors = List.of(behaviors);
}
void performAll() {
behaviors.forEach(Behavior::perform);
}
}
// Un canard peut voler ET nager
Animal duck = new Animal("Duck", new FlyBehavior(), new SwimBehavior());
duck.performAll(); // Flying + Swimming
"I favor composition over inheritance. Instead of a rigid class hierarchy, I compose behaviors. This follows the 'favor composition over inheritance' principle from the Gang of Four."
ملخص المبادئ SOLID المطبقة
| المبدأ | التمرين | التقنية |
|---|---|---|
| S — Single Responsibility | تمرين 3 (God Class) | تفكيك إلى فئات متخصصة |
| O — Open/Closed | تمرين 2 (Strategy) | إضافة بدون تعديل |
| L — Liskov Substitution | تمرين 5 (Composition) | التكوين بدلاً من الوراثة |
| I — Interface Segregation | تمرين 2 (FeeCalculator) | واجهات صغيرة وموجهة |
| D — Dependency Inversion | تمرين 3 (UserService) | الاعتماد على التجريدات، وليس على التنفيذات |