Jaki wzorzec projektowy do tego?

0

Jest 10 zmiennych do przetworzenia w różnych wariantach, a każdy wariant ma różne opcje, które potem też mają różne opcje/parametry. Potrzebny jest każdy z wariantów i próbuję to upchnać. Napiszę pseudokodem co ma to robić w tych 2 wariantach.

wariant 1:

B = fun_1(A)
C = B.isValid() ? fun_2(B) : fun_2(A)
return fun_3(C)

ok, takie coś można zrobić chain of responsibility, ale co gdy jednocześnie musi działać wariant 2:

B = fun_1(A)
C = B.isValid() ? B : fun_2(A)
return C.isValid() ? fun_3(C) : null

Do tego fun_1 i fun_2 może być po kilka naraz z różnymi parametrami do wywołania - czasem sukces przerwie dalsze wykonywanie, czasem jest kontunuowany. Do tego w zależności od parametru funkcji, fun_3 zachowuje się inaczej.

Takie coś rozwiązywałem półtorej roku temu. Zrobiłem to na ok. 50 klasach z wieloma poziomami komponentów, jedna w drugiej, druga w 3, a potem 4 w 2 ,a 2 w 5 itd. i teraz chciałbym to zrefaktoryzować, ale dochodzę do niewiele lepszych rozwiązań.

1

polecam słowno muzycznie opisać co to faktycznie robi, generalnie nie ma co na siłę pchać wszędzie wzorców projektowych, więc może kod

B = fun_1(A)
C = B.isValid() ? B : fun_2(A)
return C.isValid() ? fun_3(C) : null

będzie po prostu okej

0
hcubyc napisał(a):

polecam słowno muzycznie opisać co to faktycznie robi

Ciężko mi to opisać, ale spróbuję:
Wkażdym z tekstów znajdź wartość 10 zmiennych i zunifikuj ich wartości.

  1. Szukaj słów kluczowych zmiennej na początku linijki, jeśli znajdziesz to przyjmujesz, że wartość zmiennej jest w tej linijce i dalej w niej szukasz.
  2. Szukaj w tej linijce słowa kluczowego do danej zmiennej by znaleźć zdanie/wąski framgnet o tej zmiennej. Jeśli nie znajdziesz to szukaj znów w całym tekście zdania/wąskie fragmentu o tej zmiennej poprzez znalezienie słowa kluczowego dla danej zmiennej.
  3. Jeśli znajdziesz to pobierz wartość i przekształć ją do zunifikowanej wartości.
    np.: chcesz zmienną "salary" to na początku linijjki szukasz słowa zarobek lub płaca/ kasa itd. Fragmentu szukasz po /eur/usd/na dzień/dziennie/na miesiąc/miesięcznie/za godzinę//h itd. Wartości szukasz po \\d+ lub \\d+[\\s,\\.]\\d+/[^,.]\\d+\\s?(k/\\d+[,|.]\\d+\\s?k/\\d+\\D*\\s?(/|na)\\s?h/\\d+\\D*\\s?(/|na)\\s?dzie[ńn]. W zależności którym patternem chwycisz musisz tę wartość przekształcić na Double, przewalutować i przemnożyć go do płacy miesięcznej.

Dla innej zmiennej np. location może to wyglądać inaczej: jak znajdziesz linijkę to już nie szukasz zdania, dodatkowo jeśli nie znajdziesz linijki ani zdania ze słowami kluczowymi wskazujacymi ze tam jest ta zmienna mozesz jechac po całym tekście, bo np. szukasz tylko wystapienia słowa Toronto albo Warszawa

Generalnie, piorytety gdzie chwytać zmienną są następujące:
linijka ze zdaniem > linijka > zdanie > cały tekst

niektóre zmienne wymagają i linijki i zdania... inne lunijki lub zdania... albo nic nie wymagaja, ale piorytety przestrzegają wszystkie zmienne tzn. jeśli zidentifikowales linijke z location to weźmiesz location z tej linijki mimo ze w tekscie poza linijką też byś chwycił

3

Twoje wyjaśnienie jest bardzo enigmatyczne i dalej średnio wiadomo o co chodzi.
Co to jest ta linijka? Pierwsza linia w tekście? Czy szukasz w każdej linii, a jeśli ci się nie uda, to przechodzisz do szukania zdania/fragmentu? Czym się różni "linijka ze zdaniem" od "linijki" od "zdania"?


Przypuszczam, że chodzi ci o znalezienie wartości zmiennej w tekście, używając do tego różnych strategii szukania, więc proponowałbym zacząć od zaklepania serwisu:
Używam tutaj słowa klucza Strategy, ale nie powiązuj tego z patternem Strategy. Możesz to równie dobrze nazwać Algorytm, Technique, czy nawet Serwis

public class VariableFindingService {
	public <T> Optional<T> findVariable(String text, List<VariableFindingStrategy<T>> findingStrategies) {
		return findingStrategies.stream()
			.map(findingStrategy -> findingStrategy.findVariable(text))
			.findFirst();
	}
}

Z tego co cię zrozumiałem, to każda sprowadza się do trzech kroków:

  1. Ograniczenia tekstu do fragmentu (do linijki, zdania) na podstawie regexpa
  2. We fragmencie tekstu znalezienie wartości zmiennej na podstawie innego regexpa
  3. Znormalizowaniu wartości
class VariableFindingStrategy<T extends VariableValue> {
	private final ExtractStrategy textFragmentExtractStrategy;
	private final ExtractStrategy valueExtractStrategy;
	private final NormalizeStrategy<T> normalizeStrategy;

	public Optional<VariableValue> findVariable(String text) {
		Optional<String> textFragmentOptional = textFragmentExtractStrategy.extract(text);
		Optional<String> valueOptional = textFragmentOptional.map (textFragment -> valueExtractStrategy.extract(textFragment));
		return valueOptional.map(value -> normalizeStrategy.normalize(value));
	}
}

class ExtractStrategy {
	Optional<String> extract(String);
}

class NormalizeStrategy<T extends VariableValue> {
	Optional<T> normalize(String value);
}

No i trzeba jeszcze zaimplementować konkretny przypadek. Te strategie, to tak jak opisałeś "linijka ze zdaniem > linijka > zdanie > cały tekst".

VariableFindingStrategy<Double> salaryFindingStrategyByLine = VariableFindingStrategy.builder()
	.textFragmentExtractStrategy(text -> Pattern.from("/extractOneLine$").find(text))
	.valueExtractStrategy(text -> Pattern.from("/extractValueFromLine$").find(text))
	.normalize(value -> Double.valueOf(value) * dolarRate)
	.build();

VariableFindingStrategy<Double> salaryFindingStrategyByText = VariableFindingStrategy.builder()
	.textFragmentExtractStrategy(text -> Optional.of(text)) //Naszym fragmentem tekstu jest cały tekst
	.valueExtractStrategy(text -> Pattern.from("/extractValueFromLine$").find(text))
	.normalize(value -> Double.valueOf(value) * dolarRate)
	.build();

List<VariableFindingStrategy<Double>> salaryFindingStrategies = Arrays.asList(
	salaryFindingStrategyBySentenceLine,
	salaryFindingStrategyByLine,
	salaryFindingStrategyBySentence,
	salaryFindingStrategyByText
);

variableFindingService.findVariable(text, salaryFindingStrategies);

To można dalej poprawiać i uogólniać:

  1. Możesz stworzyć PatternExtractStrategy, żeby nie usunąć powtarzający się kod
PatternExtractStrategy implements ExtractStrategy {
	private final String patten;

	Optional<String> extract(String text) {
		Pattern.from(pattern.find(text)
	}
}

//...
.textFragmentExtractStrategy(new PatternExtractStrategy("/extractOneLine$"))
//...
  1. Możesz sobie zrobić fabrykę, która każdej zmiennej sama tworzy strategie i wtedy determinujesz ich kolejność.

  2. Jeśli z danego fragmentu tekstu masz kilka metod wyciągania wartości (np. różnymi patternami), to możesz zmodyfikować VariableFindingStrategy

class VariableFindingStrategy<T> {
	private final ExtractStrategy textFragmentExtractStrategy;
	private final List<ExtractAndNormalizeStrategy> extractAndNormalizeStrategies;

	public Optional<VariableValue> findVariable(String text) {
		textFragmentExtractStrategy.extract(text)
			.flatMap(textFragment -> extractAndNormalizeStrategies.stream()
				.map(extractAndNormalizeStrategy -> extractAndNormalizeStrategy.extractAndNormalize(textFragment))
				.findFirst());
	}
}

class ExtractAndNormalizeStrategy<T> {
	private final ExtractStrategy valueExtractStrategy;
	private final NormalizeStrategy<T> normalizeStrategy;

	Optional<T> extractAndNormalize(String textFragment) {
		return valueExtractStrategy.extract(textFragment));
			.map(value -> normalizeStrategy.normalize(value));
	}
}
  1. Strategie, które wyciągają fragment tekstu, (a w szczególności ta, która ucina linie i ta która zwraca caly tekst) będą się powtarzać. Mozesz takie obiekty stworzyć raz i ich używać wielokrotnie
0

Tyvrel, wziąłem pod uwagę Twoje porady, dzięki. Mniej więcej tak to robiłem tylko znacznie bardziej burdelowo. Co powiecie na taką implementację:

App:

Scrapper<Integer> ageScrapper = new ScrapperAgeFactory.produceDefault();
Optional<Integer> age = ageScrapper .scrapeFrom(text).findFirst();

główny scrapper, który wyciąga wartości zmiennej:

public class Scrapper<T> {

	private final TextCleaner textCleaner;
	private final LinesFindingStrategy linesFinderStrategy;
	private final VariableFindingService <T> varFindingServiceLineFound;
	private final VariableFindingService <T> varFindingServiceLineNotFound;
	private final Predicate<T> isTraitValueValid;
	
	private Scrapper(Builder<T> builder) {
		// inicjowanie pól builderem...
	}
	
	public List<T> scrapeFrom(String text) {
		text = textCleaner.apply(text);
		List<String> lines = linesFinderStrategy.find(text);
		List<T> traitValues = lines.size() > 0 ? 
				varFindingServiceLineFound.find(lines.get(0)) :
				varFindingServiceLineNotFound.find(text);
		return traitValues.stream()
				.filter(isTraitValueValid)
				.collect(Collectors.toList());
	}
	
	public static class Builder<T> {
		// implementacja buildera...
	}
}

fabryka scrapper przykładowo dla zmiennej age:

public interface ScrapperDefaultFactory<T> {

	Scrapper<T> produceDefault();
}

public class ScrapperAgeFactory implements ScrapperDefaultFactory<Integer> {

	@Override
	public Scrapper<Integer> produceDefault() {
		
		int maxAge = 120;
		int minAge = 18;
		
		return new Scrapper.Builder<Integer>()
				.setTextCleaner(new TextCleanerDefault())
				.setLineFindingStrategy(new LineFindingStrategyByKeyWord("wiek:?"))  // nie dopisuje implementacji, zwyczajnie wyciąga linie tekstu ze słowem kluczowym
				.setVarFindingServiceLineFound(new AgeFindingServiceFactory().produceDefaultLineFound())
				.setVarFindingServiceLineNotFound(new AgeFindingServiceFactory().produceDefaultLineNotFound())
				.setIsTraitValueValidPredicate(x -> x >= minAge && x <= maxAge)
				.build();
	}	
}

Service zawierają wiele Strategy, które odpalają są odpalane po kolei przez serwis:

public class VariableFindingService<T> {
	
	protected List<VariableFindingStrategy<T>> strategies = new LinkedList<>();
	
	public List<T> find(String text) {
		return strategies.stream()
				.flatMap(strategy -> strategy.find(text).stream())
				.collect(Collectors.toList());
	}
	
	public VariableFindingService<T> addFirst(VariableFindingStrategy<T> component) {
		this.strategies .add(0, component);
		return this;
	}
	
	public VariableFindingService<T> addLast(VariableFindingStrategy<T> component) {
		this.strategies .add(component);
		return this;
	}
	
	public void clearComponents() {
		strategies  onents.clear();
	}
}
public class VariableFindingStrategy<T> {

	private final String fragmentWithTraitPattern;
	private final String traitPattern;
	private final Function<String, T> normalizer;
	
	public VariableFindingStrategy(String fragmentWithTraitPattern, String traitPattern, Function<String, T> normalizer) {
		this.fragmentWithTraitPattern = fragmentWithTraitPattern;
		this.traitPattern = traitPattern;
		this.normalizer = normalizer;
	}
	
	public List<T> find(String text) {
		return new PatternFinderFunction(fragmentWithTraitPattern).apply(text).stream()  // PatternFinderFunction to zwykład funkcja do wyciągania paternów z tekstu, nie dopisuję implementacji
				.flatMap(fragment -> new PatternFinderFunction(traitPattern).apply(fragment).stream())
				.map(normalizer::apply)
				.collect(Collectors.toList());
	}
}

fabryka takiego strategy wyglądałaby przykładowo dla age tak:

public class AgeFindingServiceFactory implements VariableFindingServiceDefaultLineFoundFactory<Integer>,
		VariableFindingServiceDefaultLineNotFoundFactory<Integer> {

	@Override
	public VariableFindingService <Integer> produceDefaultLineFound() {
		VariableFindingService <Integer> service = new VariableFindingService  <>();
		return service .addFirst(new VariableFindingStrategy <Integer>("\\d+ lat", "\\d+", Integer::parseInt))
				.addFirst(new VariableFindingStrategy  <Integer>("\\d+ mies.", "\\d+", x -> Integer.parseInt(x) * 12))
				.addFirst(new VariableFindingStrategy <Integer>("\\d+ miesięcy", "\\d+", x -> Integer.parseInt(x) * 12))
				//.addFirst(new VariableFindingStrategy <Integer>("rocznik \\d{2}", x -> sysdate() - todate("19" + x)))
				.addFirst(new VariableFindingStrategy <Integer>("\\d+", "\\d+", x -> Integer.parseInt(x)));
	}

	@Override
	public VariableFindingService <Integer> produceDefaultLineNotFound() {
		VariableFindingService <Integer> service= new VariableFindingService <>();
		return service.addFirst(new VariableFindingStrategy <Integer>("\\d+ lat", "\\d+", Integer::parseInt))
				.addFirst(new VariableFindingStrategy <Integer>("\\d+ mies.", "\\d+", x -> Integer.parseInt(x) * 12))
				.addFirst(new VariableFindingStrategy <Integer>("\\d+ miesięcy", "\\d+", x -> Integer.parseInt(x) * 12));
	}
}

Może być?

0

VariableFindingStrategyCompositeDefaultLineNotFoundFactory<Integer> za dużo abstrakcji.

1

no nic... skończyłem... z ok. 1000 lini kodu zszedłem do 800 i z ok. 30 klas do 28... Ale przynajmniej ponazywane lepiej i chyba można się w tym lepiej odnaleźć...
dziękuję za Wasze porady

1 użytkowników online, w tym zalogowanych: 0, gości: 1