You wrote

>  I know the following is bad

and here is already a misconception: thinking religiously in terms of good and bad about this. My interpretation of Tell-Don't-Ask is, when you see a code snippet like

    if(a.isX){
        a.doY();
    }

you **may consider** to refactor it to `a.doYWhenX();` - or not. But before you decide about this refactoring, also check

- if `doYWhenX()` is a useful abstraction in your context

- if `doYWhenX()` has some reusage potential

- if it helps to make `a.isX` (or `a.isX()`) private

- if it makes the using code more readable

- is it worth the hassle

So my recommendation is to change your mindset about "Tell-Don't-Ask principle" - it is a rough guideline, a rule-of-thumb design heuristic, nothing more.

Now apply this to your case with two objects:

    if(a.isX && b.isP){
        a.doY();
        b.doQ();
    }

Whether it it more suitable to refactor this 

- to `a.doYQWhenXP(b);` or 

- to `b.doYQWhenXP(a);` or 

- to `myService.doYQWhenXP(a,b)`, or

- leave the code as it is

depends heavily on the context, which variant in your real world context creates highest readability, best reusability, most sensible abstraction, or best encapsulation. Meaningless names like `doX` or `isP` alone are not suitable for making such a decision.