Tworzenie nadmiarowych interfejsów.

Tworzenie nadmiarowych interfejsów.
ŁF
Moderator
  • Rejestracja:ponad 22 lata
  • Ostatnio:około 18 godzin
0

raczej:

Kopiuj
class B{ B() { this.c = new C(); this.d = new D(); } }
class A{ A() { this.b = new B(); } }

Zobacz pozostałe 6 komentarzy
caer
Wersja po dobrym zastanowieniu się to mniej poziomów zależności, a nie rezygnacja z DI
SP
ale jarekr pisał o wstrzykiwaniu przez konstruktor, a Ty to wyrzuciłeś. W moim przykładzie pokazałem, że jak mamy taki łańcuch zależności i każda klasa ma wstrzykiwanie przez konstruktor, to ostatecznie oddelegowujemy new do najwyższej klasy
Wibowit
@ŁF: dalej nie rozumiesz czym jest wstrzykiwanie zależności. new w konstruktorze oznacza brak DI. To jest proste jak budowa cepa - jak można tego nie rozumieć?
ŁF
@Wibowit, używam IoC (StructureMap, Autofac) od sześciu lat i doskonale wiem, co DI jest, a co nie. To, co napisałem w tym poście nie jest przykładem DI (chyba, że b, c, d są dostępne publicznie, fuj), jest ironicznym potraktowaniem wypowiedzi jarekr "jak byś się dobrze zastanowił to nie ma zwykle 9 new".
Wibowit
@student pro: ta najwyższa klasa to wtedy odpowiednik bindingów z google guice i wszystko gra @ŁF dalej nie rozumiem aluzji z tym 9 new. może coś mi umknęło.
caer
  • Rejestracja:około 11 lat
  • Ostatnio:10 miesięcy
  • Postów:465
0

W życiu bym nie przypuszczał że tyle osób nie może uwierzyć że dependency injection można stosować ręcznie i zaśmiecanie kodu @Inject nie jest jedyną alternatywą dla umieszczania new w każdym konstruktorze.
http://di-in-scala.github.io/#manual

ŁF
Przecież to w linku to jest kontener IoC...
caer
nie, kontener IoC jest dopiero dalej
Wibowit
Kontener DI pojawia się dopiero w akapicie "Using MacWire"
jarekr000000
  • Rejestracja:ponad 8 lat
  • Ostatnio:około 2 godziny
  • Lokalizacja:U krasnoludów - pod górą
  • Postów:4707
1

@caer: trzeba przyznać, że w Scali jest dużo łatwiej i kiedy piszę w Javie czasem mi brakuje lazy val,(i o zgrozo implicitów).
(Btw. zupełnie nie używam MacWire - nie widzę sensu - a Adam zawsze zgarnia ode mnie hejta za niego w jak się spotkamy :-) )
Ale nadal wolę napisać dwie trzy linijki więcej niż wprowadzać magię. (Przy czym jeszcze 13 lat temu w javie bez generyków - nie było wyjścia, magia dawała więcej korzyści niż szkód - od czasu jednak Java 8, kiedy można wreszcie normalnie np. "dekorować" - szala jest już zdecydowanie w druga stronę ).

Jak sobie poradzić z tymi zależnościami i testami już kiedyś zrobiłem historię na githubie:
https://github.com/javaFunAgain/magic_service_story/tree/70_CLEANING
(można branchami zacząć od początku:
https://github.com/javaFunAgain/magic_service_story/tree/10_BEGIN)


jeden i pół terabajta powinno wystarczyć każdemu
edytowany 4x, ostatnio: jarekr000000
Robakowy
  • Rejestracja:ponad 8 lat
  • Ostatnio:ponad 3 lata
  • Postów:48
0
Shalom napisał(a):

Jakoś dziwnym trafem jedyne problemy z systemem po refaktoringu i z dopasowaniem testów widzę w systemach grubo korzystających z JavaEE lub Spring

@jarekr000000 nic w tym dziwnego, bo to są pewnie zwyczajnie duze systemy. W kodzie na 200 linijek w Haskellu tego problemu nie będzie, nie dlatego ze Haskell taki dobry tylko ze złożoność problemu mniejsza. Takie podejście że wstrzykiwanie jest złe jest równie krókowzroczne co podejście new jest złe ;)

Właśnie.
Zrobić refactoring w małym projekcie jest stosunkowo prosto.
Ale w projekcie składającym się z kilkudziesięciu bibliotek to już zabawa na 2 tygodnie.

Należy jednak mierzyć siły na zamiary.
Jeżeli robimy program z założenia mały, albo który nie będzie rozwijany i z danej klasy będzie korzystać tylko jeden programista, można pominąć interfacey.
W innym przypadku należy tworzyć interfacey.
Dzięki temu można zrównoleglić pracę - jeden programista robi implementację interfacu, a drugi już robi funkcjonalność korzystającą z tego interfacu.

Wibowit
  • Rejestracja:prawie 20 lat
  • Ostatnio:około 2 godziny
1

Ja jestem tuż po wycince Google Guice'a z projektu i zastąpienia go zwykłym ręcznym DI. Zamiast tworzyć jakieś moduły, providery, pierdoły mam sobie klaskę typu:

Kopiuj
 package something.wiring

object ProductionModule {
  private val dependency6 = new Dependency6()
  private val dependency4 = new Dependency4()
  private val dependency5 = new Dependency5(dependency6)
  private val dependency1 = new Dependency1(dependency4, dependency5, dependency6)
  private val dependency2 = new Dependency2(dependency4)
  private val dependency3 = new Dependency3(dependency5)
  val application = new Appilcation(dependency1, dependency2, dependency3)
}

Cały graf w jednym miejscu. Jak na razie graf w przerobianym przeze mnie mikroserwisie jest malutki, ale nawet jakby się rozrósł do dużych rozmiarów to można go ostatecznie podzielić na klasy względem semantyki, czyli np zrobić OrdersModule, UsersModule, etc Ale to jest ostateczność, bo jeśli graf powiązań jest zbyt duży jak na jedną klasę to prawdopodobnie oznacza to, że nie mamy już mikroserwisu, a coś co zdąża w stronę kobyły.

Brak kontenera DI oznacza łatwe i wygodne testowanie. Nie trzeba robić cudów typu tworzenie ekstra modułu i łączenia go z modułami produkcyjnymi za pomocą metod 'override' po to by nie duplikować całej masy boilerplate'u z modułów Guice'owych. W testach mam osobną hierarchię zależności w budowniczym. Np:

Kopiuj
package tests.infrastructure.wiring

object ApplicationBuilder {
  def build(parameter1: Boolean, parameter2: Type1, parameter3: Option[Type2]): Application = {
    val dependency6 = new Dependency6Test()
    val dependency5 = new Dependency5(dependency6)
    val dependency1 = if (parameter1) new Dependency1VariantA(dependency5) else new Dependency1VariantB(new Dependency4())
    val dependency2 = new Dependency2Test(parameter2, dependency6)
    val dependency3 = new Dependency3Test(parameter3, dependency5)
    new Application(dependency1, dependency2, dependency3)
  }
}

To jest oczywiście do testów integracyjnych, bo dla jednostkowych nie trzeba składać całej aplikacji.

Rozwiązanie bez Guice jest czytelniejsze, zwięźlejsze i od razu widać co od czego zależy i od razu też widać czy graf zależności ma sens czy może jest skopany. Ta ostatnia cecha jest bardzo ważna, bo wrzucając adnotacje @Inject gdzie się da można zrobić tak katastrofalne hierarchie zależności, że ciężko jest potem prześledzić ich graf.

Jest prawie pełne DI, tzn konstruktory robią tylko i wyłącznie zapisywanie parametrów do prywatnych pól. Wyjątkiem są może jakieś pojedyncze aktory, które w celu inicjalizacji np wysyłają gdzieś tam wiadomość.

PS:
Nikt tu nie mówi o usunięciu wstrzykiwania. Ani ja, ani @jarekr000000. Kontener DI absolutnie nie jest potrzebny do wstrzykiwania. Kontenery DI jedyne co robią to magicznie składają graf powiązań. W ręcznym DI graf jest explicite, ale DI dalej jest. Różnica między DI, a brakiem DI jest naprawdę prosta i niezwiązana z żadną biblioteką.

Wersja klaski z brakiem DI:

Kopiuj
class A {
  private val dependency1 = new Dependency1()
  private val dependency2 = new Dependency2()
  private val dependency3 = new Dependency3()
  // tu jakiś kod, który korzysta z powyższych zależności
}

Wersja klaski z ręcznym DI:

Kopiuj
class A(dependency1: Dependency1, dependency2: Dependency2, dependency3: Dependency3) {
  // tu jakiś kod, który korzysta z powyższych zależności
}

object A extends A(Dependency1, Dependency2, Dependency3) // globalny singleton z ustawionymi zależnościami

Globalny singleton to nie jest dobra sprawa, ale to tylko pokazowo. Lepszym rozwiązaniem jest stworzenie ProductionModule, który centralizuje graf zależności, tak jak podałem na początku posta.

PS2:
Niektórym wydaje się, że główną zaletą kontenera DI jest to, że nie musimy używać słówka new. No sorry, jeśli new tak bardzo was razi w oczy to możecie sobie zrobić alias na Class.newInstance, który nie ma słówka new. W DI chodzi o testowalność. W DI konstruktor nic nie robi, ma wszystko podane na tacy i po zapisie parametrów konstruktora w obiekcie (czyli te sławetne this.cośTam = cośTam) ten obiekt jest gotowy do działania. Dzięki temu mogę tworzyć dowolne ilości instancji takiej klasy i mam pewność, że konstruktor np nie zacznie dobijać się do bazy czy wysyłać emaili. Zależności oczywiście trzeba stworzyć słówkiem new albo refleksją czy innym badziewiem (albo podać nulla jeśli wiemy że w danym scenariuszu testowym dana zależność nie będzie używana), ale ważne żeby robić to poza konstruktorami. Tyle.

DI (zarówno ręczne jak i te przez kontener DI) zwiększa testowalność, gdyż w testach testujemy metody, a nie konstruktory. Nie mogę przetestować metody bez odpalania konstruktora, więc zależy mi na tym, by konstruktor robił jak najmniej. W DI konstruktor tylko przepisuje pola, a więc nic się nie może sypnąć i działa to bardzo szybko, w sam raz się więc nadaje do testów jednostkowych.


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
edytowany 3x, ostatnio: Wibowit
Robakowy
  • Rejestracja:ponad 8 lat
  • Ostatnio:ponad 3 lata
  • Postów:48
1
Wibowit napisał(a):

Niektórym wydaje się, że główną zaletą kontenera DI jest to, że nie musimy używać słówka new. No sorry, jeśli new tak bardzo was razi w oczy to możecie sobie zrobić alias na Class.newInstance, który nie ma słówka new. W DI chodzi o testowalność.

Nieprawda.
W DI chodzi o to, aby tworzyć obiekty nie znając ich klasy. A jedynie inteface.
W "Twoim ręcznym DI" to nie jest DI, tylko przekazywanie obiektów przez konstruktor.
Jak za taką pomocą stworzysz nowy obiekt zgodny z interfejsem "IMojInterfejs" ?

W DI po to opieramy się na kontenerze i tworzeniu za jego pomocą obiektów o danym interfejsie, aby móc skompilować bibliotekę i zmieniać klasy implementujące bez rekompilacji biblioteki.
Oraz bez żadnego refaktoringu biblioteki.

Dzięki temu możemy wykorzystać bibliotekę w wielu projektach, używających, "wstrzykujących" kompletnie odmienne implementacje.

edytowany 1x, ostatnio: Robakowy
jarekr000000
No cóż, jak widać można sobie stworzyć własną definicję DI. Gratuluję!
Wibowit
Biblioteka wymagająca użycia konkretnego kontenera DI? Iście diabelski plan. "Hej ludzie, żeby używać naszej biblioteki to musicie korzystać z kontenera DI Janusz-IoC w wersji 1500.100.900, inaczej nie pyknie!"
Wibowit
Jako ciekawostkę podam, że (o ile mnie pamięć nie myli) to @Krolik pisał kiedyś, że w Apache Sparku wstawili Google Guice, ale klienci mieli taki dependency hell (a konkretnie jar hell), że nie dało się tego prosto ominąć. Myślę, że dałoby się to ogarnąć jakimś JarJarem i zrobić własną kopię Google Guice, ale Apache Spark poszedł drogą ręcznego DI, które się sprawdziło. Dobrze pamiętam?
Wibowit
Inna sprawa to to, że biblioteczka potraktowana JarJarem jest kompletnie niekompatybilna z oryginalną, więc konfiguracja Guice'a ze Sparka byłaby i tak nieużywalna dla klientów.
Robakowy
@Wibowit: Tak, zależność oparta na kontenerze DI to jedyna zależność, jaką trzeba uwzględniać w tym przypadku.
Robakowy
@jarekr000000: Nie patrz na wyczytane na blogach definicje, tylko weź jakiś kontener DI i przestudiuj co on może zrobić dla Ciebie
jarekr000000
@Robakowy: no tu się zgodze, czytanie psuje oczy, a taki kontener (DI :-) ) może dać pracę na wiele lat. Jakby nie takie kontenery to świat nie potrzebowałby tylu magików.
Wibowit
@Robakowy - no to jest już za dużo i żadna publiczna biblioteka tak nie robi. Co najwyżej frameworki, ale frameworka możesz używać tylko jednego i steruje on całym twoim kodem. Dostajesz więc potężnego vendor lock-ina.
Robakowy
@Wibowit: Publiczne może nie, ale własne tak. A co do vendor lock-ina - przecież tego się nie uniknie. Używając jakiejkolwiek biblioteki skazujesz się na nią.
jarekr000000
@Robakowy: zdajesz sobie sprawę, że wymiana bilbiloteki a wymiana frameworka to zupełnie dwa różne poziomy problemu. Dlatego zresztą frameworki raczej giną. https://www.youtube.com/watch?v=Kp5YJBQsCPE
Wibowit
@Robakowy Wszystko rozbija się o przechodnie zależności. Jeśli mój JanuszLIB wymaga 10 innych bibliotek do działania to w tym momencie użytkownik JanuszLIBa musi mieć te biblioteki w tych dokładnie wersjach i musimy się synchronizować przy aktualizacji tych przechodnich zależności. Biblioteka z własnymi zależnościami to ogromny ból, więc się tego unika. Zależności są co najwyżej wewnątrz rodziny artefaktów, czyli np janusz-lib zależy od janusz-core i janusz-util, a wszystkie janusz-xxx są wypuszczane przez jednego człowiek czy zespół i to można z łatwością synchronizować.
Wibowit
  • Rejestracja:prawie 20 lat
  • Ostatnio:około 2 godziny
0

Nieprawda.
W DI chodzi o to, aby tworzyć obiekty nie znając ich klasy. A jedynie inteface.
W "Twoim ręcznym DI" to nie jest DI, tylko przekazywanie obiektów przez konstruktor.
Jak za taką pomocą stworzysz nowy obiekt zgodny z interfejsem "IMojInterfejs" ?

Bajeczki. Tego w ogóle nie ma w definicji wstrzykiwania zależności.

Definicja wstrzykiwania zależności z polskiej Wikipedii:

Wstrzykiwanie zależności (ang. Dependency Injection, DI) – wzorzec projektowy i wzorzec architektury oprogramowania polegający na usuwaniu bezpośrednich zależności pomiędzy komponentami na rzecz architektury typu plug-in. Polega na przekazywaniu gotowych, utworzonych instancji obiektów udostępniających swoje metody i właściwości obiektom, które z nich korzystają (np. jako parametry konstruktora). Stanowi alternatywę do podejścia, gdzie obiekty tworzą instancję obiektów, z których korzystają np. we własnym konstruktorze.

Definicja z angielskiej:

In software engineering, dependency injection is a technique whereby one object supplies the dependencies of another object. A dependency is an object that can be used (a service). An injection is the passing of a dependency to a dependent object (a client) that would use it. The service is made part of the client's state.[1] Passing the service to the client, rather than allowing a client to build or find the service, is the fundamental requirement of the pattern.

This fundamental requirement means that using values (services) produced within the class from new or static methods is prohibited. The class should accept values passed in from outside.

Nigdzie nie ma mowy o jakichś interfejsach i implementacjach. To nie jest żaden wymóg. Nie jest to też niewykonalne w ręcznym DI. Mogę przecież zrobić:

Kopiuj
trait JakiśIntefejs

class JakaśImplementacja extends JakiśInterfejs

class TworzonyObiekt(zależność: JakiśInterfejs) // tutaj, powiedzmy sobie, nie znam JakiejśImplementacji i operuję na JakimśInterfejsie


object ProductionModule {
  private val tworzonyObiekt = new TworzonyObiekt(new JakaśImplementacja) // hej, TworzonyObiekt oczekiwał intefejsu, a podałem mu implementację, hurra
}

W powyższym przykładzie ProductionModule jest odpowiednikiem bindingów z kontenerów DI, bo przecież kontenerowi DI i tak trzeba powiedzieć jaką klasę wcisnąć w miejsce danego interfejsu.


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
edytowany 1x, ostatnio: Wibowit
Robakowy
  • Rejestracja:ponad 8 lat
  • Ostatnio:ponad 3 lata
  • Postów:48
0
Wibowit napisał(a):

Bajeczki. Tego w ogóle nie ma w definicji wstrzykiwania zależności.

Definicja wstrzykiwania zależności z polskiej Wikipedii:

A tu?:

Polega na przekazywaniu gotowych, utworzonych instancji obiektów udostępniających swoje metody i właściwości obiektom, które z nich korzystają (np. jako parametry konstruktora).

kontener DI właśnie tworzy obiekt i go przekazuje obiektom, które z nich korzystają.
Nie na konstruktorach się możliwości kończą.
Za pomocą DI da się utworzyć w metodzie np. listę miliona obiektów o danym interfejsie.

W "ręcznym DI" hmmm....możesz stworzyć fabrykę obiektów implementujących interface.
Ale przy zmianie klasy, musisz zmienić fabrykę.
Tak idąc do przodu dochodzimy do....własnego kontenera DI.
Po co to robić skoro już jest ? :)

Wibowit
  • Rejestracja:prawie 20 lat
  • Ostatnio:około 2 godziny
0

Klasa nie musi implementować intefejsu, by udostępniać swoje metody i właściwości obiektom. Skąd ci to przyszło do głowy?

Nie na konstruktorach się możliwości kończą.
Za pomocą DI da się utworzyć w metodzie np. listę miliona obiektów o danym interfejsie.

W "ręcznym DI" hmmm....możesz stworzyć fabrykę obiektów implementujących interface.
Ale przy zmianie klasy, musisz zmienić fabrykę.
Tak idąc do przodu dochodzimy do....własnego kontenera DI.
Po co to robić skoro już jest ? :)

W Google Guice za to muszę robić mutlibindy i przy zmianie klasy zmieniać wszystkie multibindy. Czyli roboty tyle samo, albo nawet więcej bo tych bindingów do Guice to trzeba szukać po całym projekcie.


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
edytowany 1x, ostatnio: Wibowit
vpiotr
  • Rejestracja:ponad 13 lat
  • Ostatnio:prawie 3 lata
0

@Robakowy, chyba brakuje Ci wiedzy na ten temat ale sie wypowiadasz. Jesli chcesz cwiczyc erystyke to proponuje dzial flame.

Polecam przeczytac w calosci:
https://en.m.wikipedia.org/wiki/Inversion_of_control

Koziołek
Moderator
  • Rejestracja:prawie 18 lat
  • Ostatnio:około miesiąc
  • Lokalizacja:Stacktrace
  • Postów:6821
0

Tak idąc do przodu dochodzimy do....własnego kontenera DI.
Po co to robić skoro już jest ?

Ponieważ kontenery DI czasami dodają za dużo od siebie i masz problem. Przykładem mogą być AOPy ze springa, które w pewnych przypadkach działają zbyt magicznie.

W DI chodzi o to, aby tworzyć obiekty nie znając ich klasy. A jedynie inteface.

Duże uproszczenie. Kontener DI i tak musi mieć odpowiednią konfigurację wraz z definicjami co na co się mapuje.


Sięgam tam, gdzie wzrok nie sięga… a tam NullPointerException
Shalom
  • Rejestracja:około 21 lat
  • Ostatnio:prawie 3 lata
  • Lokalizacja:Space: the final frontier
  • Postów:26433
0

@Koziołek nie zgodzę sie. Kontener musi finalnie wiedzieć co utworzyć, ale to nie znaczy ze trzeba pisać jakieś żmudne konfiguracje. Przykład z życia wzięty gdzie użycie kontenera IoC upraszcza sprawę i ułatwia życie:

Mamy system z którego można wystartować sobie różne pluginy. Jest zestaw pewnych pluginów do "inżynierii wstecznej" z których każdy działa wg dokładnie tej samej logiki, która jest objęta ładnym template method. Oczywiście ta logika ma pewne kroki uzależnione od konkretnego pluginu (bo inaczej reversuje sie kod X a inaczej kod Y). Moglibyśmy sobie w każdym z pluginów ręcznie towrzyć ten nasz "GenericReverseLogic" i wrzucać do niego np. konstruktorem obiekty odpowiedzialne za realizacje konkretnych kroków algorytmu. Tylko ze w takim razie w każdym pluginie powtarzalibyśmy dokładnie ten sam schemat z tworzeniem X obiektów i ustawianiem ich.
Alternatywnie możemy GenericReverseLogic oznaczyć jako jakiś @Named i analogicznie każdy z tych zależnych obiektów realizujących konkretne kroki i po prostu je wstrzykiwać. Zalety? Startujac plugin X kontener może automatycznie wykryć w classpath co należy utworzyć i poskłada nam to do kupy. Nie ma potrzeby pisać konfigurację wraz z definicjami co na co się mapuje., nie ma potrzeby powtarzać prawie identycznego kodu N razy (zmieniając tylko nazwy konkretnych klas).

Nie będę też juz wspominał o tym, ze przy okazji używamy kontenera też do wykrywania listy dostępnyc pluginów i do ich startowania ;] Nie potrzeba niczego ręcznie aktualizować kiedy dodajesz nowy plugin, bo zwyczajnie system przy uruchomieniu będzie widział wszystkie implementacje IPlugin.

@Wibowit ciekawi mnie jak chciałbyś podobne rzeczy zrobić bez użycia kontenera IoC i jednocześnie bez implementowania własnego bieda-IoC za pomocą jakiejś refleksji i czarnej magii / bez konieczości kopiowania identycznego kodu X razy.


"Nie brookliński most, ale przemienić w jasny, nowy dzień najsmutniejszą noc - to jest dopiero coś!"
Koziołek
Moderator
  • Rejestracja:prawie 18 lat
  • Ostatnio:około miesiąc
  • Lokalizacja:Stacktrace
  • Postów:6821
0

@Shalom: ale ten efekt masz bez typowego kontenera DI, ale z użyciem SPI.


Sięgam tam, gdzie wzrok nie sięga… a tam NullPointerException
jarekr000000
  • Rejestracja:ponad 8 lat
  • Ostatnio:około 2 godziny
  • Lokalizacja:U krasnoludów - pod górą
  • Postów:4707
1

@Shalom ... jaka refleksja? przecież @Wibowit w Scali pisze i tam tej runtimowej biedy się coraz mniej używa.

Po drugie już kilka razy pisał jak to robi - w innym wątku. I nie ma tam żadnego bieda kontenera - nie potrzeba.

Bzdura. Sorry. Zapedziłem się - piszesz o pluginach. I Racja - jak bym miał runtimowe zależności użyłbym kontenera np. typu spring.
(W jednym projekcie używamy do tego OSGi, które jest nawet gorsze miejscami od Springa (classloader hell) - ale "pluginowość" załatwia).

A tekst na górze zostawiam - jako nauczkę dla siebie: czytaj posty Shaloma dokładnie nawet z rana.


jeden i pół terabajta powinno wystarczyć każdemu
edytowany 7x, ostatnio: jarekr000000
Shalom
  • Rejestracja:około 21 lat
  • Ostatnio:prawie 3 lata
  • Lokalizacja:Space: the final frontier
  • Postów:26433
1
  1. @Koziołek service provider to przecież też jest DI jakby nie patrzeć. Z tą różnicą że w każdym pluginie będziesz pisał "implementacje" tego SPI, która będzie dokładnie identyczna, z różnicą co do nazwy klasy. Jasne, mozna, szczególnie jak komuś płacą od linijki kodu :) Jest to pewna alternatywa jeśli koniecznie chcemy się pozbyć z projektu kontenera. Zresztą to jest jasna sprawa ze kontener nigdy nie jest konieczny. Zawsze można go zastąpić czymś innym.
  2. @jarekr000000 jw. ja rozumiem ze się "da" to zrobić. Kosztem produkowania bezużytecznego, powtarzalnego kodu, który nie przynosi żadnej wartości dodanej, oprócz tego że jesteśmy czyści, bez kontenera.

"Nie brookliński most, ale przemienić w jasny, nowy dzień najsmutniejszą noc - to jest dopiero coś!"
Wibowit
  • Rejestracja:prawie 20 lat
  • Ostatnio:około 2 godziny
0

@Shalom:

Primo - rzadko kiedy pisze się programy, które mają wtyczki. Wtyczki to generalnie egzotyka, a w webówce to już w ogóle. Stąd przypadek jest mocno teoretyczny dla typowego programisty.

Secundo - najczęściej używanym przeze mnie programem, który używa wtyczek jest IntelliJ IDEA i ty też go chętnie używasz. IntelliJ jest też otwartoźródłowy, więc łatwiej będzie zajrzeć i stwierdzić jak coś rozwiązać. Wobec tego porozmawiajmy o nim, no chyba, że jest jakiś inny program do którego obaj mamy dostęp i nie musimy się domyślać jak działa, bo możemy odpalić i przejrzeć kod.

Mamy system z którego można wystartować sobie różne pluginy. Jest zestaw pewnych pluginów do "inżynierii wstecznej" z których każdy działa wg dokładnie tej samej logiki, która jest objęta ładnym template method. Oczywiście ta logika ma pewne kroki uzależnione od konkretnego pluginu (bo inaczej reversuje sie kod X a inaczej kod Y). Moglibyśmy sobie w każdym z pluginów ręcznie towrzyć ten nasz "GenericReverseLogic" i wrzucać do niego np. konstruktorem obiekty odpowiedzialne za realizacje konkretnych kroków algorytmu. Tylko ze w takim razie w każdym pluginie powtarzalibyśmy dokładnie ten sam schemat z tworzeniem X obiektów i ustawianiem ich.
Alternatywnie możemy GenericReverseLogic oznaczyć jako jakiś @named i analogicznie każdy z tych zależnych obiektów realizujących konkretne kroki i po prostu je wstrzykiwać.

Nie widzę specjalnie sensu w tym co piszesz. Wtyczka może mieć intefejs np:

Kopiuj
 class Wtyczka {
  def działaj(intellij: IntelliJ): Unit
}

I tym sposobem mamy dostęp do wszystkiego co oferuje IDE. Jeśli bardzo chcemy to możemy sobie dopisać klaskę (fasadę), która ma tylko jeden parametr intellij i serię metod, które delegują do potrzebnych nam funkcjonalności.

Zalety? Startujac plugin X kontener może automatycznie wykryć w classpath co należy utworzyć i poskłada nam to do kupy. Nie ma potrzeby pisać konfigurację wraz z definicjami co na co się mapuje., nie ma potrzeby powtarzać prawie identycznego kodu N razy (zmieniając tylko nazwy konkretnych klas).

Generalnie to co potrzebujemy przy wtyczkach to znajdowanie JARów. Myślę, że taki IntelliJ taką mapkę JARów gdzieś musi mieć, bo:

  • plugin można wyłączyć nawet jeśli jest zainstalowany,
  • plugin może się sypnąć i IDEA wyłącza go przy restarcie,

Musi gdzieś być mapka zawierająca co najmniej: (nazwa jara) -> (status). Każdy JAR jest też ładowany w osobnym classloaderze, więc nie można sobie po prostu ustawić jako classpath całego folderu. Trzeba iterować po JARach explicite. Ładowanie w osobnych classloaderach pozwala korzystać wtyczkom z niekompatybilnych wersji bibliotek. Myślę, że to podstawowa sprawa - gdyby tak nie było to mielibyśmy classpath hell.

Mechanizm wtyczek w IntelliJu daje też możliwość rozszerzania funcjonalności jednej wtyczki przez drugą wtyczkę. Żeby to zrobić to (z tego co zrozumiałem z super szybkiej i bardzo pobieżnej analizy dokumentacji) to trzeba explicite podać z jakiej wtyczki się korzysta (a więc musi to być jakiś konkretny namiar, a nie coś automatycznie wstrzyknięte), a rozszerzana wtyczka musi mieć extension points.

Czy da się to zaimplementować za pomocą Google Guice? Pewnie się da, ale trzeba się nawyginać i zostajemy z masą niepotrzebnych hacków. Google Guice nie wygląda mi na bibliotekę stworzoną z myślą o implementowaniu mechanizmu wtyczek.

PS:
Poza tym wtyczka może chcieć się zainicjalizować. Jeśli wyszukamy ją automatycznie to nie będziemy wiedzieć gdzie są jej dane. Musimy gdzieś trzymać też mapkę: (nazwa jara) -> (dane wtyczki).


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
edytowany 5x, ostatnio: Wibowit
jarekr000000
I w jednym się przede wszystkim zgadzam - Spring, Guice jest też dla ludzi, ale wrzucanie tego do typowej biednej aplikacji WEB to lekka przesada. A do bogatej zbrodnia :-)
jarekr000000
  • Rejestracja:ponad 8 lat
  • Ostatnio:około 2 godziny
  • Lokalizacja:U krasnoludów - pod górą
  • Postów:4707
1

Musi gdzieś być mapka zawierająca co najmniej: (nazwa jara) -> (status). Każdy JAR jest też ładowany w osobnym classloaderze, więc nie można sobie po prostu ustawić jako classpath całego folderu. Trzeba iterować po JARach explicite. Ładowanie w osobnych classloaderach pozwala korzystać wtyczkom z niekompatybilnych wersji bibliotek. Myślę, że to podstawowa sprawa - gdyby tak nie było to mielibyśmy classpath hell.

Tak przy okazji z tymi osobnymi classloaderami też kłopot - mam przykład OSGi, który to jest piękny w teorii, każdy jar w osobnym CL, wyspecyfikowane jawne publiczne API. Niestety w praktyce zamienia się classpath hell na classloader hell.
(oczywiście niby tylko, gdy jakaś bilbioteka, której używamy brzydko się bawi z classloaderami. W praktyce zaskakująco dużo bibliotek to robi - > część zupełnie niepotrzebnie).


jeden i pół terabajta powinno wystarczyć każdemu
edytowany 1x, ostatnio: jarekr000000
Wibowit
  • Rejestracja:prawie 20 lat
  • Ostatnio:około 2 godziny
0

No dobra. Obrońcy kontenerów DI podajcie mi jakiś konkretny przykład, gdzie użycie jakiegoś kontenera DI da zauważalny zysk w dobrze zaprojektowanej aplikacji. Z moich doświadczeń wynika, że wycięcie Google Guice (a konkretnie to Scala Guice, które jest nawet zwięźlejsze) i zastąpienie go ręcznym DI przynosi wymierne zyski w postaci:

  • zmniejszonej ilości kodu i klas (np w testach mogę zrobić prostego ifa w logice budującej graf zależności zamiast żonglować modułami z bindingami),
  • łatwiejszemu testowaniu,
  • możliwości szybkiego przejrzenia wewnętrznych zależności między klasami, bo cały gotowy graf jest explicite w jednej klasie, a to daje:
  • szybki pogląd na architekturę aplikacji,
  • łatwość zauważenia popsutej hierarchii zależności - przy rozproszonym po kodzie grafie zależności nie widać od razu czy nie popełniliśmy gdzieś błędu wrzucając klasę w niewłaściwe miejsce w hierarchii,

Dowód na wiarę o wyższości kontenerów DI nad ręcznym DI mnie nie przekonuje. Dodatkowo ZTCP taki Shalom popisał się nieznajomością DI pisząc, że bez kontenera DI będę musiał przepychać zależności w dół przez wiele poziomów. Hmm, gdzie? Przecież w DI zależności dostaję w konstruktorze i na tym koniec. Czekam więc na przykład, który rozjaśni sprawę. Ja podałem mnóstwo konkretnych przykładów, czyli kodu źródłowego.


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
edytowany 1x, ostatnio: Wibowit
Robakowy
  • Rejestracja:ponad 8 lat
  • Ostatnio:ponad 3 lata
  • Postów:48
0
Wibowit napisał(a):

No dobra. Obrońcy kontenerów DI podajcie mi jakiś konkretny przykład, gdzie użycie jakiegoś kontenera DI da zauważalny zysk w dobrze zaprojektowanej aplikacji. Z moich doświadczeń wynika, że wycięcie Google Guice (a konkretnie to Scala Guice, które jest nawet zwięźlejsze) i zastąpienie go ręcznym DI przynosi wymierne zyski w postaci:

  • zmniejszonej ilości kodu i klas (np w testach mogę zrobić prostego ifa w logice budującej graf zależności zamiast żonglować modułami z bindingami),
  • łatwiejszemu testowaniu,
  • możliwości szybkiego przejrzenia wewnętrznych zależności między klasami, bo cały gotowy graf jest explicite w jednej klasie, a to daje:
  • szybki pogląd na architekturę aplikacji,
  • łatwość zauważenia popsutej hierarchii zależności - przy rozproszonym po kodzie grafie zależności nie widać od razu czy nie popełniliśmy gdzieś błędu wrzucając klasę w niewłaściwe miejsce w hierarchii,

Masz rację, że DI oprócz swoich korzyści daje też parę utrudnień.
Korzyści DI należy rozpatrywać w długim okresie.
Tworząc obiekty za pomocą DI rzeczywiście nie widzisz od ręki wszelkich zależności. Do tego celu musisz rzucić okiem na diagram encji.
Ale również dzięki temu, w wielu przypadkach nie musisz znać tych wszystkich zależności.
Traktujesz obiekt jak "black box" skupiając się na jego udostępnianej funkcjonalności, a nie na tym co jest w środku.
DI zbuduje graf zależności za Ciebie.

Przykład.
Robisz funkcjonalność wyświetlającą okno dialogowe np. do pobrania danych do logowania do bazy danych.
Instancja okna jest tworzona wewnątrz tej funkcjonalności.
Możesz na stałe wpisać w tą funkcjonalność klasę okna i tworzyć tylko to jedno okno cały czas.
Ale możesz też wykorzystać do tego celu DI i pozwolić użytkownikowi na stworzenie dowolnego, wybranego przez niego okna.
DI znacznie zwiększa możliwości wykorzystania tego samego kodu w wielu projektach.

Dowód na wiarę o wyższości kontenerów DI nad ręcznym DI mnie nie przekonuje. Dodatkowo ZTCP taki Shalom popisał się nieznajomością DI pisząc, że bez kontenera DI będę musiał przepychać zależności w dół przez wiele poziomów. Hmm, gdzie?

Shalom napisał prawdę.
Mając obiekt z 10 innymi obiektami w konstruktorze, a każdy z tych 10 obiektów ma 3 obiekty we własnym konstruktorze, musisz tworzyć 30 obiektów ręcznie.
W DI tworzysz tylko obiekt główny, a resztę utworzy za Ciebie kontener DI.

Robakowy
  • Rejestracja:ponad 8 lat
  • Ostatnio:ponad 3 lata
  • Postów:48
0
vpiotr napisał(a):

@Robakowy, chyba brakuje Ci wiedzy na ten temat ale sie wypowiadasz. Jesli chcesz cwiczyc erystyke to proponuje dzial flame.

Polecam przeczytac w calosci:
https://en.m.wikipedia.org/wiki/Inversion_of_control

Czy możesz przytoczyć swoje argumenty na poparcie swoich odczuć w powyższym zakresie ?

Wibowit
  • Rejestracja:prawie 20 lat
  • Ostatnio:około 2 godziny
0

Masz rację, że DI oprócz swoich korzyści daje też parę utrudnień.
Korzyści DI należy rozpatrywać w długim okresie.
Tworząc obiekty za pomocą DI rzeczywiście nie widzisz od ręki wszelkich zależności. Do tego celu musisz rzucić okiem na diagram encji.
Ale również dzięki temu, w wielu przypadkach nie musisz znać tych wszystkich zależności.
Traktujesz obiekt jak "black box" skupiając się na jego udostępnianej funkcjonalności, a nie na tym co jest w środku.
DI zbuduje graf zależności za Ciebie.

Ciekawe kiedy dotrze do ciebie różnica między ręcznym DI, a refleksyjnym DI oraz między implementowaniem DI, a brakiem DI. Ja absolutnie nie chcę wyrzucać DI, bo bardzo go lubię i przydaje się do wielu rzeczy.

Shalom napisał prawdę.
Mając obiekt z 10 innymi obiektami w konstruktorze, a każdy z tych 10 obiektów ma 3 obiekty we własnym konstruktorze, musisz tworzyć 30 obiektów ręcznie.
W DI tworzysz tylko obiekt główny, a resztę utworzy za Ciebie kontener DI.

Kompletny przykład poproszę, wraz z bindingami.


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
vpiotr
  • Rejestracja:ponad 13 lat
  • Ostatnio:prawie 3 lata
0
Robakowy napisał(a):
vpiotr napisał(a):

@Robakowy, chyba brakuje Ci wiedzy na ten temat ale sie wypowiadasz. Jesli chcesz cwiczyc erystyke to proponuje dzial flame.

Polecam przeczytac w calosci:
https://en.m.wikipedia.org/wiki/Inversion_of_control

Czy możesz przytoczyć swoje argumenty na poparcie swoich odczuć w powyższym zakresie ?

Tak, mogę:

Robakowy napisał(a):

Niektórym wydaje się, że główną zaletą kontenera DI jest to, że nie musimy używać słówka new. No sorry, jeśli new tak bardzo was razi w oczy to możecie sobie zrobić alias na Class.newInstance, który nie ma słówka new. W DI chodzi o testowalność.

(tu miałem coś napisać obraźliwego ale się powstrzymałem)

W DI chodzi także o to żebyś nie wywoływał konstruktora bo możesz nie mieć wiedzy o tym jak to zrobić, albo ktoś może chcieć to od dzisiaj robić inaczej - w jednym miejscu a nie w 30.

Nieprawda.
W DI chodzi o to, aby tworzyć obiekty nie znając ich klasy. A jedynie inteface.

Nie. Chodzi o to żeby w ogóle nie tworzyć obiektu po stronie klasy-klienta. Bo jak to zrobić może zależeć od wywołującego.

W "Twoim ręcznym DI" to nie jest DI, tylko przekazywanie obiektów przez konstruktor.

Przeczytaj Wiki.

Jak za taką pomocą stworzysz nowy obiekt zgodny z interfejsem "IMojInterfejs" ?

Tak:

Kopiuj

IMojInterfejs mojObiekt = new IMojInterfejs() {
         @Override
         public void jakasMetoda() {
         }
       };

// ... przekaż (wstrzyknij) gdzies mojObiekt
mojKlient.setMojObiekt(mojObiekt);
edytowany 2x, ostatnio: vpiotr
neves
  • Rejestracja:ponad 21 lat
  • Ostatnio:4 minuty
  • Lokalizacja:Kraków
  • Postów:1114
1

Odnośnie Dependency Injection to w świecie .net mam fajny wykresik, obrazujący tradeoffs (zależności) pomiędzy ręcznym wstrzykiwaniem, a używaniem kontenera, może pomoże wam w dyskusji :)

title

obrazek pochodzi z tekstu When to use a DI Container by Mark Seemann


jarekr000000
  • Rejestracja:ponad 8 lat
  • Ostatnio:około 2 godziny
  • Lokalizacja:U krasnoludów - pod górą
  • Postów:4707
1

Nawet się z tekstem zgadzam -tylko mam inne doświadczenia.
Wolę Poor Man's DI dokładnie z tych samych powodów, dla których wolę Javę od JavaScriptu. "Stringly typed" jest skuteczne w średnim okresie, ale w końcu się odbija czkawką.


jeden i pół terabajta powinno wystarczyć każdemu
KE
  • Rejestracja:około 18 lat
  • Ostatnio:około 7 lat
0
vpiotr napisał(a):

W DI chodzi także o to żebyś nie wywoływał konstruktora bo możesz nie mieć wiedzy o tym jak to zrobić, albo ktoś może chcieć to od dzisiaj robić inaczej - w jednym miejscu a nie w 30.

Kopiuj

sorry @vpiotr ale nie rozumiesz czym jest wstrzykiwanie
konstruktor wlasnie jest po to zebys nie mial mozliwosci zepsucia obiektu przez jego czesciowe utworzenie, przez settery jak najbardziej mozesz taki obiekt zepsuc, jak mozna nie wiedziec jak utworzyc obiekt z uzyciem konstruktora, po to wałasnie jest konstruktor zebys wiedział dokladnie jak go utworzyc.

i kolejna sprawa pojecia construktor injection a field/setter injection nadal podchodza pod DI jak najbardziej, wiec nie pisz ze w DI chodzi o nie wywoływanie konstruktora.

wstrzykiwanie przez konstruktor to tak samo DI gdzie normalnie wywołujesz konstruktor i nie musisz do tego uzywac frameworku. Tutaj nikt sie nie kłóci ze DI jest do d**y, dependency lookup jest niefajne ale DI ?
a to czy uzyjesz kontenera ktory sam zlozy za ciebie zaleznosci i wywoła za ciebie konstruktor to inna bajka, jedni lubia sami to zorbic inni przez springa/guica

kila stron o DI, a wyszło od tego czy tworzyc zawsze Interface'y :)

jarekr000000
zrozumiałem, że raczej @vpiotr chodziło o hardkodowanie implementacji i robienie nietestowalengo kodu (poprzez właśnie nieumiejętne wrzucanie new (na nieodpowiednim poziomie) ) - czyli nie używanie DI.
KE
jak tak to zwracam honor.
Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)