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

الفصل 4: تصميم REST والأنماط التصميمية

الهدف

تغطي هذه التمارين تصميم واجهات برمجة التطبيقات REST وتنفيذ الأنماط التصميمية - وهما فئتان شائعتان من الأسئلة في مقابلات الخلفية Java/Spring Boot.


التمرين 1 — تصميم نقطة نهاية REST كاملة​

"صمم ونفذ نقطة نهاية لإنشاء واسترجاع المستخدمين."

الحل​

@RestController
@RequestMapping("/api/users")
public class UserController {

private final UserService userService;

@PostMapping
public ResponseEntity<UserDTO> create(@Valid @RequestBody CreateUserRequest req) {
UserDTO user = userService.create(req);
return ResponseEntity.status(HttpStatus.CREATED).body(user);
}

@GetMapping("/{id}")
public ResponseEntity<UserDTO> getById(@PathVariable Long id) {
return userService.findById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}

@GetMapping
public ResponseEntity<Page<UserDTO>> list(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {
return ResponseEntity.ok(userService.findAll(PageRequest.of(page, size)));
}
}

كائنات نقل البيانات (DTOs)​

public record CreateUserRequest(
@NotBlank String name,
@Email String email
) {}

public record UserDTO(Long id, String name, String email) {}

النقاط الرئيسية التي يجب ذكرها في المقابلة​

ممارسة جيدةلماذا
DTOs بدلاً من الكياناتلا تعرض الكيانات JPA في الـ API
@Validتحقق من صحة المدخلات على جانب الخادم
201 Created لـ POSTرموز HTTP الصحيحة
404 Not Found إذا لم يكن موجودًاإرجاع الحالة الصحيحة
التقسيم في القوائملا ترجع كل البيانات دفعة واحدة
سجلات Java لـ DTOsغير قابلة للتغيير، مختصرة، تولد تلقائيًا equals/hashCode/toString
عبارة المقابلة

"I use DTOs to avoid exposing entities, @Valid for input validation, proper HTTP status codes (201 Created, 404 Not Found), and pagination for list endpoints. I use Java Records for immutable DTOs."


التمرين 2 — تصميم واجهة برمجة تطبيقات للتجارة الإلكترونية​

"صمم نقاط النهاية REST لنظام التجارة الإلكترونية."

الحل​

Products:
GET /api/products → قائمة المنتجات مع تقسيم
GET /api/products/{id} → تفاصيل المنتج
POST /api/products → إنشاء منتج (ADMIN)
PUT /api/products/{id} → تعديل منتج (ADMIN)
DELETE /api/products/{id} → حذف منتج (ADMIN)

Orders:
POST /api/orders → تقديم طلب
GET /api/orders → طلباتي (المستخدم المتصل)
GET /api/orders/{id} → تفاصيل الطلب
PATCH /api/orders/{id}/status → تعديل الحالة (ADMIN)

Auth:
POST /api/auth/register → تسجيل
POST /api/auth/login → تسجيل الدخول (يعيد JWT)
POST /api/auth/refresh → تحديث التوكن

Cart:
GET /api/cart → سلتي
POST /api/cart/items → إضافة عنصر إلى السلة
PUT /api/cart/items/{id} → تعديل الكمية
DELETE /api/cart/items/{id} → إزالة من السلة

الممارسات الجيدة لـ REST المذكورة​

القاعدةالمثال
الأسماء بصيغة الجمع/products ليس /product
أفعال HTTP = الإجراءاتGET = قراءة، POST = إنشاء، PUT = استبدال، PATCH = تعديل جزئي، DELETE = حذف
لا أفعال في URL/api/orders ليس /api/getOrders
الموارد المتداخلة/api/cart/items (العناصر تنتمي إلى السلة)
الأمان حسب الدورADMIN للعمليات الحساسة

التمرين 3 — تنفيذ نمط Builder​

"أنشئ فئة HttpRequest غير قابلة للتغيير باستخدام نمط Builder."

الحل​

public final class HttpRequest {
private final String url;
private final String method;
private final Map<String, String> headers;
private final String body;

private HttpRequest(Builder builder) {
this.url = Objects.requireNonNull(builder.url, "URL required");
this.method = builder.method;
this.headers = Map.copyOf(builder.headers); // نسخة دفاعية
this.body = builder.body;
}

public String getUrl() { return url; }
public String getMethod() { return method; }
public Map<String, String> getHeaders() { return headers; }
public String getBody() { return body; }

public static class Builder {
private String url;
private String method = "GET";
private final Map<String, String> headers = new HashMap<>();
private String body;

public Builder url(String url) { this.url = url; return this; }
public Builder method(String m) { this.method = m; return this; }
public Builder header(String k, String v) { headers.put(k, v); return this; }
public Builder body(String b) { this.body = b; return this; }
public HttpRequest build() { return new HttpRequest(this); }
}
}

الاستخدام​

HttpRequest req = new HttpRequest.Builder()
.url("https://api.sonar.com/issues")
.method("POST")
.header("Authorization", "Bearer token123")
.header("Content-Type", "application/json")
.body("{\"severity\": \"CRITICAL\"}")
.build();

النقاط الرئيسية​

التقنيةلماذا
فئة finalيمنع الوراثة
حقول finalعدم القابلية للتغيير
منشئ privateفرض استخدام الـ Builder
Map.copyOf()نسخة دفاعية - لا يمكن تعديل الكائن بعد الإنشاء
Objects.requireNonNull()التحقق من الحقول المطلوبة
واجهة برمجة سلسة (return this)تسلسل الاستدعاءات
عبارة المقابلة

"Immutable object, defensive copies for collections, required fields validated in constructor. The fluent API makes the call site self-documenting."


التمرين 4 — تنفيذ نمط Singleton (آمن للخيوط)​

"نفذ Singleton آمن للخيوط في Java."

الحل الحديث (Enum Singleton)​

public enum DatabaseConnection {
INSTANCE;

private final Connection connection;

DatabaseConnection() {
this.connection = createConnection();
}

public Connection getConnection() {
return connection;
}

private Connection createConnection() {
// منطق الاتصال
return DriverManager.getConnection("jdbc:postgresql://localhost/mydb");
}
}

// الاستخدام
Connection conn = DatabaseConnection.INSTANCE.getConnection();

الحل الكلاسيكي (Double-Checked Locking)​

public class DatabaseConnection {
private static volatile DatabaseConnection instance;
private final Connection connection;

private DatabaseConnection() {
this.connection = createConnection();
}

public static DatabaseConnection getInstance() {
if (instance == null) {
synchronized (DatabaseConnection.class) {
if (instance == null) {
instance = new DatabaseConnection();
}
}
}
return instance;
}
}
عبارة المقابلة

"I prefer the enum approach — it's the simplest, thread-safe by default, and handles serialization. Joshua Bloch recommends it in Effective Java."

Note Spring Boot

في Spring Boot، نادرًا ما يتم تنفيذ نمط Singleton يدويًا. الحبوب Spring هي singleton افتراضيًا في حاوية IoC. إذا طرح المحاور السؤال، اذكر أنك تستخدم @Service أو @Component التي تكون singleton افتراضيًا.


التمرين 5 — نمط Observer مع أحداث Spring​

"نفذ نظام إشعارات غير مرتبط."

الحل مع أحداث Spring​

// الحدث
public class OrderCreatedEvent {
private final Order order;
public OrderCreatedEvent(Order order) { this.order = order; }
public Order getOrder() { return order; }
}

// الناشر (الخدمة التي تنشئ الطلب)
@Service
public class OrderService {
private final ApplicationEventPublisher eventPublisher;

@Transactional
public Order createOrder(OrderRequest req) {
Order order = orderRepository.save(new Order(req));
eventPublisher.publishEvent(new OrderCreatedEvent(order));
return order;
}
}

// المستمعون (يتفاعلون مع الحدث)
@Component
class EmailNotificationListener {
@EventListener
public void onOrderCreated(OrderCreatedEvent event) {
emailService.sendConfirmation(event.getOrder());
}
}

@Component
class InventoryListener {
@EventListener
public void onOrderCreated(OrderCreatedEvent event) {
inventoryService.decrementStock(event.getOrder().getItems());
}
}

@Component
class AnalyticsListener {
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void onOrderCreated(OrderCreatedEvent event) {
analyticsService.trackOrder(event.getOrder());
}
}

المزايا​

بدون Observerمع Observer
OrderService يعرف EmailService، InventoryService، AnalyticsService...OrderService ينشر حدثًا، هذا كل شيء
إضافة مستمع = تعديل OrderServiceإضافة مستمع = إنشاء فئة، صفر تعديل
ترابط قويعدم ترابط كامل
عبارة المقابلة

"I use Spring Events for the Observer Pattern. The OrderService just publishes an event — it doesn't know who listens. Adding a new reaction is just creating a new @EventListener class."


ملخص الأنماط التصميمية​

النمطالمشكلة المحلولةمثال Spring
Strategyتنويع خوارزميةFeeCalculator مع Map
Builderبناء كائنات معقدةHttpRequest.Builder
Singletonمثيل فريدBeans Spring (افتراضيًا)
Observerإشعارات غير مرتبطة@EventListener
Factoryإنشاء كائنات دون معرفة النوعBeanFactory
Template Methodهيكل خوارزميةJdbcTemplate, RestTemplate
Proxyاعتراض شفاف@Transactional, @Cacheable (proxies AOP)

⬅️ الفصل 3: الخوارزميات | الفصل 5: تصحيح الأخطاء ومراجعة الكود →