مخزن اشیا (تا پایان جلسه پانزدهم)
سطح سوال: ساده
آنچه از این جلسه باید بدانید: آشنایی با انواع داده عام (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