💻 Prep UAS DDP2 (Notes)
📃 Table of Contents
Semangat UAS nya guys :D
Current progress:
Additional resources:
Credits:
- David Liman (Soal Fast Track)
- Cindy Olivia Chai (PPT Pak Aprinaldi)
- Asfara Quaneisha (Soal latihan Pak Abi)
Buat lompat ke bagian tertentu, pake outline yang ada di bagian kanan page ini.
🌟 Soal Fast Track
📥 Download semua file
2️⃣ Soal II: Romawi Matrix
Main.java
public class Main {
public static void main(String[] args) {
System.out.println("=== DEMO OPERASI MATRIKS ANGKA ROMAWI ===\n");
RomawiMatriks romawiMatriks = new RomawiMatriks();
Romawi[][] matriks1 = {
{ new Romawi("IX"), new Romawi("IV") },
{ new Romawi("V"), new Romawi("II") }
};
Romawi[][] matriks2 = {
{ new Romawi("III"), new Romawi("VI") },
{ new Romawi("I"), new Romawi("X") }
};
System.out.println("=== Perkalian Matriks ===");
Number[][] hasilKali = romawiMatriks.multiplyMatrix(matriks1, matriks2);
GenericMatrix.printResult(matriks1, matriks2, hasilKali, '*');
System.out.println();
System.out.println("=== Penambahan Matriks ===");
Number[][] hasilTambah = romawiMatriks.addMatrix(matriks1, matriks2);
GenericMatrix.printResult(matriks1, matriks2, hasilTambah, '+');
System.out.println();
System.out.println("=== Informasi Matriks 1 ===");
int totalElemen = romawiMatriks.hitungElemen(matriks1);
Romawi jumlahElemen = romawiMatriks.jumlahElemen(matriks1);
System.out.println("Banyaknya elemen dalam Matriks 1 : " + totalElemen);
System.out.println("Jumlah total nilai di Matriks 1 : " + jumlahElemen + " (" + jumlahElemen.intValue() + ")");
System.out.println();
System.out.println("=== Simulasi Validasi Simbol Salah ===");
try {
System.out.println("Mencoba membuat angka Romawi 'LC'");
new Romawi("LC");
} catch (ExceptionInvalidRomeNumber e) {
System.out.println("Terjadi Exception: " + e.getMessage());
}
try {
System.out.println("\nMencoba membuat angka Romawi 'Z'");
new Romawi("Z");
} catch (ExceptionInvalidRomeNumber e) {
System.out.println("Terjadi Exception: " + e.getMessage());
}
try {
System.out.println("\nMencoba membuat angka Romawi dari bilangan negatif (-15)");
new Romawi(-15);
} catch (ExceptionInvalidRomeNumber e) {
System.out.println("Terjadi Exception: " + e.getMessage());
}
}
}
Romawi.java
import java.util.regex.Pattern;
public class Romawi extends Number implements Comparable<Romawi> {
private int value;
private String roman_text;
private static final String roman_regex = "^(C|XC|L?X{0,3})(IX|IV|V?I{0,3})$";
private static final Pattern PATTERN = Pattern.compile(roman_regex);
public Romawi(int value) {
if (value < 0) {
throw new ExceptionInvalidRomeNumber("Symbol -" + value + " bukan bilangan romawi!");
}
this.value = value;
this.roman_text = intToRoman(value);
}
public Romawi(String roman_string) {
if (roman_string == null || roman_string.trim().isEmpty()) throw new ExceptionInvalidRomeNumber("Symbol " + roman_string + " bukan bilangan romawi!");
String sanitized = roman_string.trim().toUpperCase();
// validasi regexxxxx
if (!PATTERN.matcher(sanitized).matches()) throw new ExceptionInvalidRomeNumber("Symbol " + roman_string + " bukan bilangan romawi!");
this.value = romanToInt(sanitized);
this.roman_text = sanitized;
}
private int romanToInt(String roman) {
int total = 0;
int previousVal = 0;
for (int i = roman.length()-1; i >= 0; i--) {
int currentVal = charToValue(roman.charAt(i));
if (currentVal < previousVal) {
total -= currentVal;
} else {
total += currentVal;
}
previousVal = currentVal;
}
return total;
}
private int charToValue(char c) {
switch(c) {
case 'I': return 1;
case 'V': return 5;
case 'X': return 10;
case 'L': return 50;
case 'C': return 100;
default: throw new ExceptionInvalidRomeNumber("Symbol " + c + " bukan bilangan romawi!");
}
}
private String intToRoman(int num) {
if (num == 0) return "0";
int[] values = {100,90,50,40,10,9,5,4,1};
String[] symbols = {"C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"};
StringBuilder sb = new StringBuilder();
for (int i = 0; i < values.length && num > 0; i++) {
while (num >= values[i]) {
num -= values[i];
sb.append(symbols[i]);
}
}
return sb.toString();
}
@Override
public String toString() {
return roman_text;
}
@Override public int intValue() { return value; }
@Override public long longValue() { return value; }
@Override public float floatValue() { return (float) value; }
@Override public double doubleValue() { return (double) value; }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Romawi)) return false;
return this.value == ((Romawi) o).value;
}
@Override
public int compareTo(Romawi o) {
return Integer.compare(this.value, o.value);
}
}
RomawiMatriks.java
public class RomawiMatriks extends GenericMatrix<Romawi> {
@Override
protected Romawi add(Romawi o1, Romawi o2) {
return new Romawi(o1.intValue() + o2.intValue());
}
@Override
protected Romawi multiply(Romawi o1, Romawi o2) {
return new Romawi(o1.intValue() * o2.intValue());
}
@Override
protected Romawi zero() {
return new Romawi(0);
}
}
Exception
public class ExceptionInvalidRomeNumber extends RuntimeException {
public ExceptionInvalidRomeNumber(String s) {
super(s);
}
public ExceptionInvalidRomeNumber(String s, Throwable t) {
super(s, t);
}
}
GenericMatrix.java (Template)
public abstract class GenericMatrix<E extends Number> {
/** Abstract method for adding two elements of the matrices */
protected abstract E add(E o1, E o2);
/** Abstract method for multiplying two elements of the matrices */
protected abstract E multiply(E o1, E o2);
/** Abstract method for defining zero for the matrix element */
protected abstract E zero();
/** Add two matrices */
public E[][] addMatrix(E[][] matrix1, E[][] matrix2) {
// Check bounds of the two matrices
if ((matrix1.length != matrix2.length) || // banyaknya baris
(matrix1[0].length != matrix2[0].length)) { // banyaknya kolom
throw new RuntimeException(
"The matrices do not have the same size");
}
//E[][] result = new E[][] ; // tidak bisa, karena type erasure java.
E[][] result =
(E[][])new Number[matrix1.length][matrix1[0].length];
// Perform addition
for (int i = 0; i < result.length; i++)
for (int j = 0; j < result[i].length; j++) {
result[i][j] = add(matrix1[i][j],matrix2[i][j]) ;
}
return result;
}
public int hitungElemen(E[][] matriks){
int count = 0;
for(int i=0 ; i<matriks.length; i++)
for(int j=0; j<matriks[0].length; j++)
count++;
return count;
}
public E jumlahElemen(E[][] matriks){
E jumlah = zero();
for(int i=0 ; i<matriks.length; i++)
for(int j=0; j<matriks[0].length; j++)
jumlah = add(jumlah,matriks[i][j]);
return jumlah;
}
/** Multiply two matrices */
public E[][] multiplyMatrix(E[][] matrix1, E[][] matrix2) {
// Check bounds
if (matrix1[0].length != matrix2.length) {
throw new RuntimeException(
"The matrices do not have compatible size");
}
// Create result matrix
E[][] result =
(E[][])new Number[matrix1.length][matrix2[0].length];
// Perform multiplication of two matrices
for (int i = 0; i < result.length; i++) {
for (int j = 0; j < result[0].length; j++) {
result[i][j] = zero();
for (int k = 0; k < matrix1[0].length; k++) {
result[i][j] = add(result[i][j],
multiply(matrix1[i][k], matrix2[k][j]));
}
}
}
return result;
}
/** Print matrices, the operator, and their operation result */
public static void printResult(
Number[][] m1, Number[][] m2, Number[][] m3, char op) {
for (int i = 0; i < m1.length; i++) {
for (int j = 0; j < m1[0].length; j++)
System.out.print(" " + m1[i][j]);
if (i == m1.length / 2)
System.out.print(" " + op + " ");
else
System.out.print(" ");
for (int j = 0; j < m2.length; j++)
System.out.print(" " + m2[i][j]);
if (i == m1.length / 2)
System.out.print(" = ");
else
System.out.print(" ");
for (int j = 0; j < m3.length; j++)
System.out.print(m3[i][j] + " ");
System.out.println();
}
}
}
4️⃣ Soal IV: Crazy Eight
SOLID Principles Analysis
Singkatan dari SOLID:
- Single Responsibility Principles (SRP)
Intinya satu class/method cuman boleh punya satu tugas atau tujuan yang spesifik -> pecahin beberapa hal besar, menjadi lebih spesifik lagi. Tujuannya biar kode lebih kecil, gampang buat di test dan debug, dan menghindari coupling. - Open / Close Principles
Intinya kode harus terbuka dalam artian saat diperluas, tapi tertutup dalam modifikasi (sebisa mungkin jangan dimodifikasi kedepannya kecuali class/method paling bawah). Tujuannya pas ada fitur baru, cukup tambahin / turunin class baru tanpa ubah class utama, untuk ngejamin legacy code tetep didukung. - Liskov Substitution Principles
Intinya kelas anak harus bisa menggantikan induknya tanpa ketergantungan, ini buat ngejamin inheritance yang dilakuin udah benar dan kelas anak tetep patuh ke spesifikasi dan "kontrak" yang dimiliki superclass -> makanya kenapa kita pakai interface, biar nggak ketergantungan seandainya class parent rusak/hilang. - Interface Segregation Principles
Intinya lebih baik bikin interface yang kecil, spesifik, daripada bikin satau interface gede. Tujuannya? biar lebih modular, jadi gak semua method di enforce ke kelas kelas lainnya, misalnya kasus Burung, ada burung yang terbang sama burung yang gak terbang, disinilah kita harus pecah jadi dua interface -> burungTerbang dan burungGakTerbang (misal burung onta). - Dependency Inversion Principles
Intinya program sebisa mungkin bergantung ke abstraksi kyk interface, jangan ke hal yang konkret, kelas induk (super) gak boleh bergantung ke kelas anak (sub) buat berfungsi dengan baik, jadi usahain class parent gak pakai metode atau bergantung ke class childrennya.
Apakah CrazyEight sudah mengikuti SOLID principles dengan baik?
1. Single Responsibility Principle (SRP)
Status: Cukup Baik, namun ada kekurangan.
- Sudah Baik: Kelas
Card,CardCollection,Deck, danHandmemiliki tanggung jawab yang cukup spesifik.Cardfokus pada data kartu, sedangkanCardCollectiondan turunannya fokus pada manajemen kumpulan kartu. - Kekurangan:
* KelasPlayermemiliki terlalu banyak tanggung jawab: mengelola tangan (Hand), melakukan logika permainan (play,searchForMatch,drawForMatch), dan menghitung skor.
* KelasEightsmenggabungkan logika permainan dengan input/output (UI). PenggunaanScannerdanSystem.out.printlnlangsung di dalam kelas ini mempersulit pengujian otomatis dan penggantian antarmuka (misalnya jika ingin pindah ke GUI).
2. Open/Closed Principle (OCP)
Status: Kurang Baik.
- Masalah: Kode ini tidak "terbuka terhadap ekstensi" tetapi "tertutup terhadap modifikasi".
* Aturan permainan (seperti logikacardMatchesdiPlayer) bersifat hardcoded. Jika ingin mengubah aturan (misalnya kartu 8 bukan lagi wildcard), kita harus mengubah kode sumber kelas tersebut.
* Logika permainan di kelasEightsjuga kaku. Menambahkan aturan baru atau variasi permainan akan mengharuskan modifikasi besar pada metodeplayGamedantakeTurn.
3. Liskov Substitution Principle (LSP)
Status: Baik.
- Analisis: Kelas
DeckdanHandadalah turunan dariCardCollection. Sejauh ini, mereka dapat menggantikanCardCollectiontanpa merusak perilaku sistem. Tidak ada metode diCardCollectionyang di-override dengan cara yang mempersempit pra-kondisi atau memperluas pasca-kondisi secara ilegal.
4. Interface Segregation Principle (ISP)
Status: Tidak Relevan (Belum Ada Interface).
- Analisis: Proyek ini belum menggunakan interface sama sekali. Meskipun tidak ada "fat interface" yang dilanggar, kurangnya penggunaan interface membuat sistem menjadi kaku dan sulit untuk dikembangkan (berkaitan dengan DIP).
5. Dependency Inversion Principle (DIP)
Status: Buruk (Pelanggaran Signifikan).
- Masalah:
* Ketergantungan Tinggi: Kelas tingkat tinggi sepertiEightsbergantung langsung pada implementasi konkret (Player,Hand,Deck,Scanner) alih-alih abstraksi.
* Circular Dependency: Terjadi ketergantungan melingkar antaraEightsdanPlayer. MetodePlayer.play(Eights eights, Card prev)mengharuskanPlayermengenal kelasEights, sementaraEightsjuga mengenalPlayer. Ini adalah praktik buruk yang membuat kedua kelas sulit dipisahkan atau diuji secara terisolasi.
Kesimpulan dan Rekomendasi
Proyek CrazyEight belum sepenuhnya menerapkan prinsip SOLID dengan baik.
Rekomendasi Perbaikan:
- Gunakan Interface: Buat interface untuk
Playerdan logika pencocokan kartu agar aturan permainan bisa diganti tanpa mengubah kelas yang ada. - Pisahkan UI dan Logika: Pindahkan penggunaan
Scannerdanprintlnke kelas khusus UI/View. - Hapus Circular Dependency: Gunakan pola desain seperti Strategy atau Observer agar
Playertidak perlu mengenal kelasEightssecara langsung. - Ekstrak Logika Skor: Pindahkan perhitungan skor ke kelas khusus (misalnya
ScoreCalculator).
Penambahan Game Cangkul
File yang diubah/ditambah:
CrazyEight
Cangkulan.java (+)
CangkulanPlayer.java (+)
IPlayer.java (+)
Cangkulan.java
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class Cangkulan {
private List<IPlayer> players; // List ini boleh berisi objek dari class apa pun, asalkan class tersebut mengimplementasikan interface IPlayer
private Deck drawPile;
private Hand discardPile;
private Scanner in;
public Cangkulan(int numPlayers) {
if (numPlayers < 2 || numPlayers > 5) {
throw new IllegalArgumentException("Jumlah pemain harus antara 2 hingga 5.");
}
drawPile = new Deck("Draw Pile");
drawPile.shuffle();
players = new ArrayList<>();
for (int i = 1; i <= numPlayers; i++) {
CangkulanPlayer player = new CangkulanPlayer("Pemain " + i);
drawPile.deal(player.getHand(), 7); // Bagikan 7 kartu per pemain
players.add(player);
}
discardPile = new Hand("Discards");
in = new Scanner(System.in);
}
public boolean isDone() {
for (IPlayer p : players) {
if (p.getHand().isEmpty()) {
return true;
}
}
return false;
}
private int getCangkulanValue(Card card) {
if (card.getRank() == 1) { // Ace lebih tinggi dari King
return 14;
}
return card.getRank();
}
public void playGame() {
int leadingPlayerIndex = 0; // Pemain 1 memulai ronde pertama
while (!isDone()) {
System.out.println("\n--- RONDE BARU ---");
List<Card> trickCards = new ArrayList<>();
List<IPlayer> trickPlayers = new ArrayList<>();
Card leadingCard = null;
int highestValue = -1;
int winnerIndex = leadingPlayerIndex;
// Setiap pemain bergiliran dalam satu ronde (trick)
for (int i = 0; i < players.size(); i++) {
int currentPlayerIndex = (leadingPlayerIndex + i) % players.size();
IPlayer currentPlayer = players.get(currentPlayerIndex);
System.out.println("Giliran: " + currentPlayer.getName());
// Tampilkan tangan pemain (Optional)
currentPlayer.display();
Card playedCard = currentPlayer.play(leadingCard, drawPile);
if (playedCard != null) {
System.out.println(currentPlayer.getName() + " mengeluarkan kartu " + playedCard);
trickCards.add(playedCard);
trickPlayers.add(currentPlayer);
discardPile.addCard(playedCard);
if (leadingCard == null) {
leadingCard = playedCard;
highestValue = getCangkulanValue(playedCard);
winnerIndex = currentPlayerIndex;
} else {
// Jika jenis sama dan nilainya lebih tinggi
if (playedCard.getSuit() == leadingCard.getSuit()) {
int val = getCangkulanValue(playedCard);
if (val > highestValue) {
highestValue = val;
winnerIndex = currentPlayerIndex;
}
}
}
}
// Cek jika pemain ini kehabisan kartu dan menang, hentikan trick
if (currentPlayer.getHand().isEmpty()) {
System.out.println(">>> " + currentPlayer.getName() + " KEHABISAN KARTU DAN MENANG! <<<");
return;
}
System.out.println("-----------------");
}
System.out.println("Pemenang ronde ini adalah: " + players.get(winnerIndex).getName());
leadingPlayerIndex = winnerIndex; // Pemenang jalan pertama di ronde berikutnya
System.out.println("Ketik ENTER untuk lanjut...");
in.nextLine();
}
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print("Masukkan jumlah pemain Cangkulan (2-5): ");
int numPlayers = 2;
if (scanner.hasNextInt()) {
numPlayers = scanner.nextInt();
}
Cangkulan game = new Cangkulan(Math.max(2, Math.min(5, numPlayers)));
game.playGame();
scanner.close();
}
}
CangkulanPlayer.java
public class CangkulanPlayer implements IPlayer {
private String name;
private Hand hand;
// DIP: UI diisolasi atau disederhanakan, tidak hardcoded ke game logic yang kaku.
public CangkulanPlayer(String name) {
this.name = name;
this.hand = new Hand(name);
}
@Override
public String getName() {
return name;
}
@Override
public Hand getHand() {
return hand;
}
@Override
public boolean hasSuit(int suit) {
for (int i = 0; i < hand.size(); i++) {
if (hand.getCard(i).getSuit() == suit) {
return true;
}
}
return false;
}
@Override
public Card play(Card leadingCard, CardCollection drawPile) {
if (leadingCard == null) {
// First to play, play the first card in hand (or random/strategy)
return hand.popCard(0);
}
int requiredSuit = leadingCard.getSuit();
// Cangkul (Draw) if no matching suit
while (!hasSuit(requiredSuit) && !drawPile.isEmpty()) {
Card drawn = drawPile.popCard();
System.out.println(name + " mengcangkul (draw) " + drawn);
hand.addCard(drawn);
}
// Play the matching suit card
for (int i = 0; i < hand.size(); i++) {
if (hand.getCard(i).getSuit() == requiredSuit) {
return hand.popCard(i);
}
}
// If draw pile is empty and still no suit, just discard any card
if (!hand.isEmpty()) {
return hand.popCard(0);
}
return null;
}
@Override
public void display() {
hand.display();
}
}
IPlayer.java (Interface)
public interface IPlayer {
String getName();
Hand getHand();
Card play(Card leadingCard, CardCollection drawPile);
boolean hasSuit(int suit);
void display();
}
🌟 Soal Latihan (Pak Abie)
1️⃣ Soal I: Inheritance & Abstract Class
Anda diminta untuk membuat aplikasi pemesanan makanan MakaraCafe. Anda harus mengimplementasikan sistem penentu promo harian berdasarkan kategori keanggotaan pelanggan dan kategori menu favorit mereka.
Telah disediakan sebuah abstract class sebagai kerangka dasar sistem:
abstract class PromoSystem {
String username;
String membershipCategory;
String favoriteCategory;
PromoSystem(String username, String membershipCategory, String favoriteCategory) {
this.username = username;
this.membershipCategory = membershipCategory;
this.favoriteCategory = favoriteCategory;
}
abstract String getPromo();
}
Tugas Anda adalah membuat sebuah inherited class bernama MakaraPromo yang meng-extends class PromoSystem. Class ini bertugas untuk memberikan string promo yang sesuai. Method getPromo() harus mengembalikan string promo atau pesan penolakan berdasarkan aturan berikut:
Aturan Kategori Menu & Pilihan Promo:
Menu favorit dibagi menjadi empat kategori. Setiap kategori memiliki tiga pilihan promo yang harus dikembalikan secara acak (random) jika pelanggan berhak mendapatkan promo tersebut.
- Kopi: Diskon 10% Kopi Susu, Gratis Espresso Shot, Kopi Beli 1 Gratis 1. (Format output:
"Today's Promo: [Nama Promo]") - Camilan: Diskon Kentang Goreng, Gratis Mendoan, Ekstra Saus Keju. (Format output:
"Today's Promo: [Nama Promo]") - Makanan: Diskon Nasi Goreng, Gratis Telur Ceplok, Potongan Harga Ayam Penyet. (Format output:
"Today's Promo: [Nama Promo]") - Dessert: Diskon Brownies, Gratis Waffle, Ekstra Scoop Es Krim. (Format output:
"Today's Promo: [Nama Promo]") - Kategori Tidak Dikenali: Jika input menu tidak ada di atas, kembalikan string:
"Kategori menu tidak dikenali."
Batasan:
- Jika
membershipCategorypelanggan adalah “Newbie” dan menu favoritnya adalah “Makanan” atau “Dessert”, maka methodgetPromo()harus mengembalikan string:"Promo Makanan dan Dessert belum tersedia untuk tingkat Newbie." - Jika
membershipCategorypelanggan adalah “Member” dan menu favoritnya adalah “Dessert”, maka methodgetPromo()harus mengembalikan string:"Promo Dessert khusus untuk pelanggan VIP."
Contoh Kasus Eksekusi Soal 1:
- Kasus 1:
new MakaraPromo("Joko", "Newbie", "Makanan")memanggilgetPromo()akan mengembalikan"Promo Makanan dan Dessert belum tersedia untuk tingkat Newbie." - Kasus 2:
new MakaraPromo("Siti", "VIP", "Kopi")memanggilgetPromo()akan mengembalikan salah satu dari tiga promo Kopi secara acak, misalnya"Today's Promo: Gratis Espresso Shot". - Kasus 3:
new MakaraPromo("Budi", "Member", "Seafood")memanggilgetPromo()akan mengembalikan"Kategori menu tidak dikenali."
Unit Test
import static org.junit.Assert.*;
import org.junit.Test;
public class MakaraPromoTest {
@Test
public void testNewbieMakananRestriction() {
MakaraPromo advisor = new MakaraPromo("Joko", "Newbie", "Makanan");
assertEquals(
"Promo Makanan dan Dessert belum tersedia untuk tingkat Newbie.",
advisor.getPromo()
);
}
@Test
public void testNewbieDessertRestriction() {
MakaraPromo advisor = new MakaraPromo("Ayu", "Newbie", "Dessert");
assertEquals(
"Promo Makanan dan Dessert belum tersedia untuk tingkat Newbie.",
advisor.getPromo()
);
}
@Test
public void testMemberDessertRestriction() {
MakaraPromo advisor = new MakaraPromo("Bima", "Member", "Dessert");
assertEquals(
"Promo Dessert khusus untuk pelanggan VIP.",
advisor.getPromo()
);
}
@Test
public void testKopiPromo() {
MakaraPromo advisor = new MakaraPromo("Siti", "VIP", "Kopi");
String result = advisor.getPromo();
assertTrue(
result.equals("Today's Promo: Diskon 10% Kopi Susu") ||
result.equals("Today's Promo: Gratis Espresso Shot") ||
result.equals("Today's Promo: Kopi Beli 1 Gratis 1")
);
}
@Test
public void testDessertPromoVIP() {
MakaraPromo advisor = new MakaraPromo("Reza", "VIP", "Dessert");
String result = advisor.getPromo();
assertTrue(
result.equals("Today's Promo: Diskon Brownies") ||
result.equals("Today's Promo: Gratis Waffle") ||
result.equals("Today's Promo: Ekstra Scoop Es Krim")
);
}
@Test
public void testInvalidCategory() {
MakaraPromo advisor = new MakaraPromo("Tari", "Member", "Seafood");
assertEquals(
"Kategori menu tidak dikenali.",
advisor.getPromo()
);
}
@Test
public void testBoundarySubscribed4() {
MakaraPromo advisor = new MakaraPromo("Eko", "VIP", "Makanan");
String result = advisor.getPromo();
assertTrue(result.startsWith("Today's Promo:"));
}
}
Solusi (To Be Added)
Empty
2️⃣ Soal II: Exception Handling
Untuk meningkatkan keandalan aplikasi, anda memutuskan untuk mengubah cara penanganan kesalahan pada kategori menu. Sistem tidak boleh lagi sekadar mengembalikan string biasa ketika menerima kategori yang tidak valid, melainkan harus melempar sebuah Exception.
Tugas Anda:
- Buat Custom Exception
InvalidMenuCategoryException:
- Class ini harus merupakan sebuah Checked Exception yang menerima sebuah
String message
- Modifikasi Perilaku Evaluasi Promo:
- Di dalam class
MakaraPromo, tambahkan sebuah method baru bernamagetPromoStrict()yang mengembalikanString. - Method ini harus mendeklarasikan klausa
throws InvalidMenuCategoryException. - Logika algoritma di dalamnya sama persis dengan
getPromo()pada Soal 1. Namun, jikafavoriteCategorytidak dikenali (bukan Kopi, Camilan, Makanan, atau Dessert), sistem tidak mengembalikan string “Kategori menu tidak dikenali.”. - Sebagai gantinya, sistem harus melempar
InvalidMenuCategoryExceptiondengan format pesan:"Kategori menu [nama_kategori] tidak valid!".
Contoh Kasus Eksekusi Soal 2:
- Kasus 1:
new MakaraPromo("Ayu", "VIP", "Kopi")memanggilgetPromoStrict()akan berjalan normal dan mengembalikan string promo Kopi secara acak. - Kasus 2:
new MakaraPromo("Budi", "Newbie", "Seafood")memanggilgetPromoStrict()akan menyebabkan kompilator mewajibkan try-catch (karena checked exception). Saat dieksekusi, method akan melempar exception dengan pesan"Kategori menu Seafood tidak valid!".
Unit Test
import static org.junit.Assert.*;
import org.junit.Test;
public class MakaraPromoExceptionTest {
@Test
public void testValidCategoryDoesNotThrow() {
MakaraPromo advisor = new MakaraPromo("Andi", "Member", "Kopi");
// Memastikan tidak ada exception yang terlempar untuk kategori yang valid
String result = "";
try {
result = advisor.getPromoStrict();
} catch (InvalidMenuCategoryException e) {
fail("Exception should not have been thrown");
}
assertTrue(result.startsWith("Today's Promo:"));
}
@Test
public void testInvalidCategoryThrowsException() {
MakaraPromo advisor = new MakaraPromo("Budi", "Newbie", "Seafood");
// Memastikan exception terlempar
InvalidMenuCategoryException exception = assertThrows(
InvalidMenuCategoryException.class,
() -> {
advisor.getPromoStrict();
}
);
// Memastikan pesan error sesuai dengan format yang diminta
assertEquals("Kategori menu Seafood tidak valid!", exception.getMessage());
}
@Test
public void testAnotherInvalidCategoryThrowsException() {
MakaraPromo advisor = new MakaraPromo("Siti", "VIP", "Minuman Dingin");
InvalidMenuCategoryException exception = assertThrows(
InvalidMenuCategoryException.class,
() -> {
advisor.getPromoStrict();
}
);
assertEquals("Kategori menu Minuman Dingin tidak valid!", exception.getMessage());
}
}
Solusi (To Be Added)
Empty
3️⃣ Soal III: Generics & Wildcard
Seiring berkembangnya aplikasi MakaraCafe, sistem membutuhkan struktur data untuk mencatat riwayat promo. Karena di masa depan mungkin akan ada tipe sistem promo lain, Anda diminta merancang penyimpanan yang fleksibel menggunakan Generics dan fungsi utilmenggunakan Wildcard.
Tugas Anda:
- Buat Generic Class
PromoHistory<T>:
- Miliki data field privat bernama
historybertipeList<T>. - Buat constructor tanpa parameter yang menginisialisasi
historydengan objekArrayList. - Buat method
void addPromo(T promo)untuk menambahkan elemen ke dalam riwayat. - Buat method
T getLastPromo()yang mengembalikan elemen terakhir dari riwayat. Jika riwayat kosong, kembalikannull.
- Buat Class
PromoUtil:
- Buat static method bernama
countPromosByUsername. - Method ini menerima dua parameter: (1) Sebuah
Listyang menggunakan upper-bounded wildcard sehingga dapat menerima List dari tipe apapun yang merupakan turunan dariPromoSystem, dan (2) SebuahString username. - Method mengembalikan (
int) jumlah objek di dalam list tersebut yang propertiusername-nya cocok dengan parameter kedua.
Contoh Kasus Eksekusi Soal 3:
- Kasus Generics: Jika dibuat
PromoHistory<MakaraPromo> history = new PromoHistory<>(), lalu ditambahkan data promo milik “Siti”, maka pemanggilanhistory.getLastPromo().usernameakan menghasilkan"Siti". - Kasus Wildcard: Jika diberikan sebuah
List<MakaraPromo>berisi 3 objek (2 objek milik “Budi”, 1 objek milik “Ayu”), pemanggilanPromoUtil.countPromosByUsername(list, "Budi")akan mengembalikan nilai2.
Unit Test
import static org.junit.Assert.*;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
public class PromoGenericsTest {
@Test
public void testPromoHistoryGenerics() {
PromoHistory<MakaraPromo> history = new PromoHistory<>();
MakaraPromo promo1 = new MakaraPromo("Budi", "VIP", "Kopi");
MakaraPromo promo2 = new MakaraPromo("Siti", "Newbie", "Camilan");
// Memastikan history diinisialisasi kosong (null saat diminta getLastPromo)
assertNull(history.getLastPromo());
history.addPromo(promo1);
history.addPromo(promo2);
// Memastikan tipe T terjaga dan method bekerja
assertEquals("Siti", history.getLastPromo().username);
assertEquals("Newbie", history.getLastPromo().membershipCategory);
}
@Test
public void testWildcardUtilCount() {
List<MakaraPromo> listPromo = new ArrayList<>();
listPromo.add(new MakaraPromo("Budi", "VIP", "Kopi"));
listPromo.add(new MakaraPromo("Budi", "Member", "Dessert"));
listPromo.add(new MakaraPromo("Ayu", "VIP", "Makanan"));
// Wildcard method harus bisa menerima List<MakaraPromo>
int countBudi = PromoUtil.countPromosByUsername(listPromo, "Budi");
assertEquals(2, countBudi);
int countAyu = PromoUtil.countPromosByUsername(listPromo, "Ayu");
assertEquals(1, countAyu);
int countJoko = PromoUtil.countPromosByUsername(listPromo, "Joko");
assertEquals(0, countJoko);
}
}
Solusi (To Be Added)
Empty
🌟 Soal Latihan (Pak Apri)
1️⃣ Soal I: Kerucut
📜 Lihat soal disini
📩 Download semua file
Main.java
package KerucutSort;
import java.util.Collections;
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Kerucut> kerucuts = new ArrayList<>();
kerucuts.add(new Kerucut(4, 6, 1));
kerucuts.add(new Kerucut(6, 7, 2));
kerucuts.add(new Kerucut(10, 20, 3));
Collections.sort(kerucuts, new SortKerucut());
for (Kerucut kerucut : kerucuts) {
System.out.println(kerucut.toString());
}
}
}
Kerucut.java
package KerucutSort;
public class Kerucut {
private int id;
private double alas;
private double tinggi;
public Kerucut(double alas, double tinggi, int id) {
this.alas = alas;
this.tinggi = tinggi;
this.id = id;
}
public double getTinggi() {
return tinggi;
}
public double getVolume() {
return Math.round((1.0/3.0)*Math.PI*(Math.pow(this.alas,2))*this.tinggi);
}
public String toString() {
return "Kerucut ke-" + id + "\nAlas: " + alas + "\nTinggi: " + tinggi + "\nVolume: " + this.getVolume() + "\n";
}
}
SortKerucut.java
package KerucutSort;
import java.util.Comparator;
public class SortKerucut implements Comparator<Kerucut> {
@Override
public int compare(Kerucut a, Kerucut b) {
return Double.compare(a.getVolume(), b.getVolume());
}
}