تمرین‌های آموزشی

مخزن اشیا (تا پایان جلسه پانزدهم)

سطح سوال: ساده

آنچه از این جلسه باید بدانید: آشنایی با انواع داده عام (Generics)

علی برای نگهداری کاربران در برنامه‎ی خود، از کلاس UserRepository استفاده می‎کند. پیاده‌سازی این کلاس به صورت زیر است:

public class UserRepository {
	private Map<String, User> data = new HashMap<>();
	private IdGenerator idGenerator;

	public UserRepository(IdGenerator idGenerator) {
		this.idGenerator = idGenerator;
	}

	public void save(User user) {
		user.setId((String) idGenerator.generate());
		// implementation
	}

	public void update(User user) {
		// implementation
	}

	public User load(String id) {
		// implementation
	}

	public List<User> loadAll() {
		// implementation
	}

	public void delete(String id) {
		// implementation
	}

	public void deleteAll() {
		// implementation
	}
}

که کاربر یک شی از نوع User است:

public class User {
	private String id;
	private String name;
	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
}

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

برای ذخیره‌ی کاربران از یک نگاشت map استفاده می‎کند که کلید آن شناسه id کاربر و مقدار آن شی کاربر است. هم‌چنین هنگام ذخیره‌ی کاربر، شناسه‎ی کاربر را به وسیله idGenerator تولید می‎کند:

public interface IdGenerator {
	Object generate();
}

مدیر پروژه در جلسه‎ای به علی می‎گوید باید انواع دیگری از اشیا را نیز جداگانه ذخیره کند. به عبارت دیگر علی می‎بایست به ازای هر یک از انواع اشیا، یک کلاس مشابه با UserRepository ایجاد کند. علی که از copy paste متنفر و عاشق استفاده مجدد از کد (code reuse) است، دوست دارد از ارث‎بری (Inheritance) استفاده کند، یعنی یک کلاس AbstractRepository ایجاد کرده و به ازای هر شی‌ای که می‎خواهد ذخیره کند، یک زیرکلاس از AbstractRepository بسازد. او برای این کار با چالش‎های زیر روبرو است:

  • هر شی یک نوع متمایز دارد.
  • شناسه d هر شی می‎تواند از انواع مختلف باشد (رشته، عدد و …).

بنابراین، این ارث‎بری به این سادگی‎ها قابل انجام نیست. او از دوستش سهیل، که تجربه‎ی بیشتری دارد، با ارسال یک ایمیل، راهنمایی می‎خواهد. سهیل به او مطالعه‎ی بخش کلاس‎های عام (generic) را از سایت جاواکاپ پیشنهاد می‎دهد و از آن‎جایی که قرار است ماهی‎گیری به او یاد دهد برای کمک به او چند واسط (interface) به همراه کمی توضیح ایمیل می‎کند:

  • کلاس هر شی، یک ویژگی شناسه از انواع مختلف دارد. آن کلاس باید این واسط را پیاده‎سازی کند:
public interface IEntity<I> {
	void setId(I id);
	I getId();
  • به دلیل اینکه شناسه از انواع مختلف ‎می‎تواند باشد، بنابراین IdGenerator نیز باید بتواند شناسه از انواع مختلف تولید کند:
public interface IdGenerator<U> {
	U generate();
}
  • بنابراین واسط IRepository اینگونه تعریف می‎شود:
public interface IRepository<U, T extends IEntity<U>> {
	void save(T entity);
	void update(T entity);
	T load(U id);
	List<T> loadAll();
	void delete(U id);
	void deleteAll();

}

برای تمرین، کارهایی را که قرار است علی انجام دهد، شما انجام دهید. فایل‌هایی (IRepository.java, IdGenerator.java, IEntity.java) که سهیل به علی ایمیل زده است را می‌توانید در بسته‌ی ir.javacup.db مشاهده کنید.

کاری که باید انجام دهید به این شرح است:

  • تعریف کلاس انتزاعی (abstract) با نام AbstractRepository که IRepository را پیاده‎سازی می‎کند. به‎طوری که در حالت پیش‎فرض، زیرکلاس‎های AbstractRepository لازم نباشد چیزی در بدنه‎ی خود داشته باشند.

بنابراین اگر عبارات زیر بدون خطای کامپایل باشند:

IEntity<String> user = new User();
IEntity<Long> person = new Person();

IdGenerator<String> stringIdGenerator = new StringIdGenerator();
IdGenerator<Long> numericIdGenerator = new NumericIdGenerator();

عبارات زیر نیز باید بدون خطای کامپایل باشند:

IRepository<String, User> userRepository = new UserRepository(stringIdGenerator);
AbstractRepository<Long, Person> personRepository = new PersonRepository(numericIdGenerator);

رفتار متدهایی که در کلاس AbstractRepository پیاده‌سازی می‌کنید، به این شرح است:

  • رفتار متد load: شناسه‌ی (id) شی را به عنوان ورودی گرفته و شی‌ای با آن شناسه را برمی‌گرداند. اگر شی‌ای با آن شناسه یافت نشد، مقدار null برگردانده می‌شود.
  • رفتار متد loadAll: تمامی اشیای موجود در مخزن را در قالب یک لیست برمی‌گرداند.
  • رفتار متد save: یک شی به عنوان ورودی دریافت کرده و آن را در مخزن ذخیره می‌کند. اگر شی ورودی null باشد، باید یک استثنا از نوع IllegalArgumentException و با پیام مناسب پرتاب کند. قبل از ذخیره‌ی این شی در مخزن، باید یک شناسه توسط idGenerator به آن تخصیص داده شود.
  • رفتار متد update: یک شی به عنوان ورودی دریافت کرده و با توجه به شناسه‌ی شی، شی ورودی را جایگزین شی قدیمی در مخزن می‌کند. اگر شناسه‌ی شی ورودی null باشد، باید یک استثنا با از نوع IllegalArgumentException با پیام مناسب پرتاب کند. اگر شی‌ای با این شناسه در مخزن وجود نداشته باشد، باید یک استثنا از نوع RuntimeException با پیام مناسب پرتاب کند.
  • رفتار متد delete: یک شناسه به عنوان ورودی دریافت کرده و شی‌ای با آن شناسه را از مخزن حذف می‌کند. اگر شی‌ای با این شناسه در این مخزن وجود نداشته باشد، باید یک استثنا از نوع RuntimeException با پیام مناسب دریافت کند.
  • رفتار متد deleteAll: تمامی اشیای موجود در مخزن را حذف می‌کند.

آنچه باید آپلود کنید:

یک فایل زیپ شامل بسته‌ی ir.javacup.db است. به صورتی که وقتی فایل زیپ را باز می‌کنیم، دقیقا شاخه‌ی ir را ببینیم که درون آن شاخه‌ی javacup و درون آن نیز شاخه‌ی db قرار دارد. در داخل شاخه‌ی db فقط فایل AbstractRepository.java وجود دارد.

برای داوری تمرین، می‌توانید پاسخ خود را در سایت Quera به نحوی که در بالا گفته شد، بارگذاری کنید.

 

با ما همراه باشید:

آدرس کانال تلگرام: IranianJavaDevelopers@

آدرس صفحه اینستاگرام: javacup.ir

آدرس گروه لینکدین: Iranian Java Developers

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا