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

الفصل 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 clausesreturn في بداية الطريقة تزيل الحالات غير الصالحة مبكرًا
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)الاعتماد على التجريدات، وليس على التنفيذات

⬅️ الفصل 1: JPA & BDD | الفصل 3: الخوارزميات →